mirror of
				https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
				synced 2025-10-31 08:51:04 +08:00 
			
		
		
		
	Added ResolveDomainOperator
This commit is contained in:
		
							parent
							
								
									71aaa824ec
								
							
						
					
					
						commit
						82ad8a5df8
					
				
							
								
								
									
										4
									
								
								backend/dist/sub-store-parser.loon.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								backend/dist/sub-store-parser.loon.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1,7 +1,9 @@ | |||||||
| import { HTTP } from '../../vendor/open-api'; | import { HTTP } from '../../vendor/open-api'; | ||||||
|  | import { isIPv4, isIPv6 } from '../../utils'; | ||||||
| import { FULL } from '../../utils/logical'; | import { FULL } from '../../utils/logical'; | ||||||
| import { getFlag } from '../../utils/geo'; | import { getFlag } from '../../utils/geo'; | ||||||
| import lodash from 'lodash'; | import lodash from 'lodash'; | ||||||
|  | import $ from '../app'; | ||||||
| 
 | 
 | ||||||
| // force to set some properties (e.g., skip-cert-verify, udp, tfo, etc.)
 | // force to set some properties (e.g., skip-cert-verify, udp, tfo, etc.)
 | ||||||
| function SetPropertyOperator({ key, value }) { | function SetPropertyOperator({ key, value }) { | ||||||
| @ -222,6 +224,109 @@ function ScriptOperator(script, targetPlatform, $arguments) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const DOMAIN_RESOLVERS = { | ||||||
|  |     Google: async function (domain) { | ||||||
|  |         const resp = await $.http.get({ | ||||||
|  |             url: `https://8.8.4.4/resolve?name=${encodeURIComponent( | ||||||
|  |                 domain, | ||||||
|  |             )}&type=A`,
 | ||||||
|  |             headers: { | ||||||
|  |                 accept: 'application/dns-json', | ||||||
|  |             }, | ||||||
|  |         }); | ||||||
|  |         const body = JSON.parse(resp.body); | ||||||
|  |         if (body['Status'] !== 0) { | ||||||
|  |             throw new Error(`Status is ${body['Status']}`); | ||||||
|  |         } | ||||||
|  |         const answers = body['Answer']; | ||||||
|  |         if (answers.length === 0) { | ||||||
|  |             throw new Error('No answers'); | ||||||
|  |         } | ||||||
|  |         return answers[answers.length - 1].data; | ||||||
|  |     }, | ||||||
|  |     'IP-API': async function (domain) { | ||||||
|  |         const resp = await $.http.get({ | ||||||
|  |             url: `http://ip-api.com/json/${encodeURIComponent( | ||||||
|  |                 domain, | ||||||
|  |             )}?lang=zh-CN`,
 | ||||||
|  |         }); | ||||||
|  |         const body = JSON.parse(resp.body); | ||||||
|  |         if (body['status'] !== 'success') { | ||||||
|  |             throw new Error(`Status is ${body['status']}`); | ||||||
|  |         } | ||||||
|  |         return body.query; | ||||||
|  |     }, | ||||||
|  |     Cloudflare: async function (domain) { | ||||||
|  |         const resp = await $.http.get({ | ||||||
|  |             url: `https://1.0.0.1/dns-query?name=${encodeURIComponent( | ||||||
|  |                 domain, | ||||||
|  |             )}&type=A`,
 | ||||||
|  |             headers: { | ||||||
|  |                 accept: 'application/dns-json', | ||||||
|  |             }, | ||||||
|  |         }); | ||||||
|  |         const body = JSON.parse(resp.body); | ||||||
|  |         if (body['Status'] !== 0) { | ||||||
|  |             throw new Error(`Status is ${body['Status']}`); | ||||||
|  |         } | ||||||
|  |         const answers = body['Answer']; | ||||||
|  |         if (answers.length === 0) { | ||||||
|  |             throw new Error('No answers'); | ||||||
|  |         } | ||||||
|  |         return answers[answers.length - 1].data; | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function ResolveDomainOperator({ provider }) { | ||||||
|  |     const resolver = DOMAIN_RESOLVERS[provider]; | ||||||
|  |     if (!resolver) { | ||||||
|  |         throw new Error(`Cannot find resolver: ${provider}`); | ||||||
|  |     } | ||||||
|  |     return { | ||||||
|  |         name: 'Resolve Domain Operator', | ||||||
|  |         func: async (proxies) => { | ||||||
|  |             const results = {}; | ||||||
|  |             const resolves = new Map(); | ||||||
|  | 
 | ||||||
|  |             for (const proxy of proxies) { | ||||||
|  |                 const domain = proxy.server; | ||||||
|  |                 if (isIP(domain)) continue; | ||||||
|  |                 if (!resolves.has(domain)) { | ||||||
|  |                     resolves.set( | ||||||
|  |                         domain, | ||||||
|  |                         resolver(domain) | ||||||
|  |                             .then((ip) => { | ||||||
|  |                                 results[domain] = ip; | ||||||
|  |                                 $.info( | ||||||
|  |                                     `Successfully resolved domain: ${domain} ➟ ${ip}`, | ||||||
|  |                                 ); | ||||||
|  |                             }) | ||||||
|  |                             .catch((err) => { | ||||||
|  |                                 $.error( | ||||||
|  |                                     `Failed to resolve domain: ${domain} with resolver [${provider}]: ${err}`, | ||||||
|  |                                 ); | ||||||
|  |                             }), | ||||||
|  |                     ); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // resolve domains
 | ||||||
|  |             await Promise.all([...resolves.values()]); | ||||||
|  |             proxies.forEach((proxy) => { | ||||||
|  |                 proxy.server = results[proxy.server]; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return proxies; | ||||||
|  |         }, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isIP(ip) { | ||||||
|  |     return isIPv4(ip) || isIPv6(ip); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResolveDomainOperator.resolver = DOMAIN_RESOLVERS; | ||||||
|  | 
 | ||||||
| /**************************** Filters ***************************************/ | /**************************** Filters ***************************************/ | ||||||
| // filter useless proxies
 | // filter useless proxies
 | ||||||
| function UselessFilter() { | function UselessFilter() { | ||||||
| @ -305,7 +410,7 @@ function TypeFilter(types) { | |||||||
| 
 | 
 | ||||||
|  function filter(proxies) { |  function filter(proxies) { | ||||||
|         return proxies.map(p => { |         return proxies.map(p => { | ||||||
|             return p.name.indexOf("🇭🇰") !== -1; |             return p.name.indexOf('🇭🇰') !== -1; | ||||||
|         }); |         }); | ||||||
|      } |      } | ||||||
| 
 | 
 | ||||||
| @ -347,6 +452,7 @@ export default { | |||||||
|     'Regex Delete Operator': RegexDeleteOperator, |     'Regex Delete Operator': RegexDeleteOperator, | ||||||
|     'Script Operator': ScriptOperator, |     'Script Operator': ScriptOperator, | ||||||
|     'Handle Duplicate Operator': HandleDuplicateOperator, |     'Handle Duplicate Operator': HandleDuplicateOperator, | ||||||
|  |     'Resolve Domain Operator': ResolveDomainOperator, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| async function ApplyFilter(filter, objs) { | async function ApplyFilter(filter, objs) { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ export const SETTINGS_KEY = 'settings'; | |||||||
| export const SUBS_KEY = 'subs'; | export const SUBS_KEY = 'subs'; | ||||||
| export const COLLECTIONS_KEY = 'collections'; | export const COLLECTIONS_KEY = 'collections'; | ||||||
| export const ARTIFACTS_KEY = 'artifacts'; | export const ARTIFACTS_KEY = 'artifacts'; | ||||||
|  | export const RULES_KEY = 'rules'; | ||||||
| export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup'; | export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup'; | ||||||
| export const GIST_BACKUP_FILE_NAME = 'Sub-Store'; | export const GIST_BACKUP_FILE_NAME = 'Sub-Store'; | ||||||
| export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository'; | export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository'; | ||||||
|  | |||||||
| @ -3,9 +3,8 @@ import { | |||||||
|     GIST_BACKUP_KEY, |     GIST_BACKUP_KEY, | ||||||
|     GIST_BACKUP_FILE_NAME, |     GIST_BACKUP_FILE_NAME, | ||||||
| } from './constants'; | } from './constants'; | ||||||
| import { ENV } from '../vendor/open-api'; | import { ENV, HTTP } from '../vendor/open-api'; | ||||||
| import express from '../vendor/express'; | import express from '../vendor/express'; | ||||||
| import IP_API from '../utils/ip-api'; |  | ||||||
| import Gist from '../utils/gist'; | import Gist from '../utils/gist'; | ||||||
| import $ from '../core/app'; | import $ from '../core/app'; | ||||||
| 
 | 
 | ||||||
| @ -116,3 +115,12 @@ async function gistBackup(req, res) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | async function IP_API(req, res) { | ||||||
|  |     const server = decodeURIComponent(req.params.server); | ||||||
|  |     const $http = HTTP(); | ||||||
|  |     const result = await $http | ||||||
|  |         .get(`http://ip-api.com/json/${server}?lang=zh-CN`) | ||||||
|  |         .then((resp) => JSON.parse(resp.body)); | ||||||
|  |     res.json(result); | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								backend/src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								backend/src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | // source: https://stackoverflow.com/a/36760050
 | ||||||
|  | const IPV4_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/; | ||||||
|  | 
 | ||||||
|  | // source: https://ihateregex.io/expr/ipv6/
 | ||||||
|  | const IPV6_REGEX = | ||||||
|  |     /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; | ||||||
|  | 
 | ||||||
|  | function isIPv4(ip) { | ||||||
|  |     return IPV4_REGEX.test(ip); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isIPv6(ip) { | ||||||
|  |     return IPV6_REGEX.test(ip); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { isIPv4, isIPv6 }; | ||||||
| @ -1,10 +0,0 @@ | |||||||
| import { HTTP } from '../vendor/open-api'; |  | ||||||
| 
 |  | ||||||
| export default async function IP_API(req, res) { |  | ||||||
|     const server = decodeURIComponent(req.params.server); |  | ||||||
|     const $http = HTTP(); |  | ||||||
|     const result = await $http |  | ||||||
|         .get(`http://ip-api.com/json/${server}?lang=zh-CN`) |  | ||||||
|         .then((resp) => JSON.parse(resp.body)); |  | ||||||
|     res.json(result); |  | ||||||
| } |  | ||||||
							
								
								
									
										4
									
								
								backend/sub-store.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								backend/sub-store.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										86
									
								
								web/src/components/ResolveDomainOperator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								web/src/components/ResolveDomainOperator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | <template> | ||||||
|  |   <v-card class="ml-1 mr-1 mb-1 mt-1"> | ||||||
|  |     <v-card-title> | ||||||
|  |       <v-icon color="primary" left>dns</v-icon> | ||||||
|  |       节点域名解析 | ||||||
|  |       <v-spacer></v-spacer> | ||||||
|  |       <v-btn icon @click="$emit('up', idx)"> | ||||||
|  |         <v-icon>keyboard_arrow_up</v-icon> | ||||||
|  |       </v-btn> | ||||||
|  |       <v-btn icon @click="$emit('down', idx)"> | ||||||
|  |         <v-icon>keyboard_arrow_down</v-icon> | ||||||
|  |       </v-btn> | ||||||
|  |       <v-btn icon @click="$emit('deleteProcess', idx)"> | ||||||
|  |         <v-icon color="error">mdi-delete</v-icon> | ||||||
|  |       </v-btn> | ||||||
|  |       <v-dialog> | ||||||
|  |         <template #activator="{on}"> | ||||||
|  |           <v-btn v-on="on" icon> | ||||||
|  |             <v-icon>help</v-icon> | ||||||
|  |           </v-btn> | ||||||
|  |         </template> | ||||||
|  |         <v-card> | ||||||
|  |           <v-card-title class="headline"> | ||||||
|  |             节点域名解析 | ||||||
|  |           </v-card-title> | ||||||
|  |           <v-card-text> | ||||||
|  |             将节点域名解析成 IP 地址 | ||||||
|  |           </v-card-text> | ||||||
|  |         </v-card> | ||||||
|  |       </v-dialog> | ||||||
|  |     </v-card-title> | ||||||
|  |     <v-card-text> | ||||||
|  |       服务提供商 | ||||||
|  |       <v-radio-group v-model="provider"> | ||||||
|  |         <v-row> | ||||||
|  |           <v-col> | ||||||
|  |             <v-radio label="Google" value="Google"/> | ||||||
|  |           </v-col> | ||||||
|  |           <v-col> | ||||||
|  |             <v-radio label="IP-API" value="IP-API"/> | ||||||
|  |           </v-col> | ||||||
|  |           <v-col> | ||||||
|  |             <v-radio label="Cloudflare" value="Cloudflare"/> | ||||||
|  |           </v-col> | ||||||
|  |         </v-row> | ||||||
|  |       </v-radio-group> | ||||||
|  |     </v-card-text> | ||||||
|  |   </v-card> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: ["args"], | ||||||
|  |   data: function () { | ||||||
|  |     return { | ||||||
|  |       idx: this.$vnode.key, | ||||||
|  |       provider: "Google" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     if (typeof this.args !== "undefined") { | ||||||
|  |       this.provider = this.args.provider || "Google"; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     save() { | ||||||
|  |       this.$emit("dataChanged", { | ||||||
|  |         idx: this.idx, | ||||||
|  |         args: { | ||||||
|  |           provider: this.provider | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     provider() { | ||||||
|  |       this.save(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
| @ -223,6 +223,7 @@ import ScriptFilter from "@/components/ScriptFilter"; | |||||||
| import ScriptOperator from "@/components/ScriptOperator"; | import ScriptOperator from "@/components/ScriptOperator"; | ||||||
| import RegexSortOperator from "@/components/RegexSortOperator"; | import RegexSortOperator from "@/components/RegexSortOperator"; | ||||||
| import HandleDuplicateOperator from "@/components/HandleDuplicateOperator"; | import HandleDuplicateOperator from "@/components/HandleDuplicateOperator"; | ||||||
|  | import ResolveDomainOperator from "@/components/ResolveDomainOperator"; | ||||||
| 
 | 
 | ||||||
| const AVAILABLE_PROCESSORS = { | const AVAILABLE_PROCESSORS = { | ||||||
|   "Flag Operator": { |   "Flag Operator": { | ||||||
| @ -261,6 +262,10 @@ const AVAILABLE_PROCESSORS = { | |||||||
|     component: "HandleDuplicateOperator", |     component: "HandleDuplicateOperator", | ||||||
|     name: "节点去重" |     name: "节点去重" | ||||||
|   }, |   }, | ||||||
|  |   "Resolve Domain Operator": { | ||||||
|  |     component: "ResolveDomainOperator", | ||||||
|  |     name: "节点域名解析" | ||||||
|  |   }, | ||||||
|   "Script Filter": { |   "Script Filter": { | ||||||
|     component: "ScriptFilter", |     component: "ScriptFilter", | ||||||
|     name: "脚本过滤器" |     name: "脚本过滤器" | ||||||
| @ -291,7 +296,8 @@ export default { | |||||||
|     RegexDeleteOperator, |     RegexDeleteOperator, | ||||||
|     ScriptFilter, |     ScriptFilter, | ||||||
|     ScriptOperator, |     ScriptOperator, | ||||||
|     HandleDuplicateOperator |     HandleDuplicateOperator, | ||||||
|  |     ResolveDomainOperator | ||||||
|   }, |   }, | ||||||
|   data: function () { |   data: function () { | ||||||
|     return { |     return { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Peng-YM
						Peng-YM