Added ResolveDomainOperator

This commit is contained in:
Peng-YM 2022-06-07 15:55:30 +08:00
parent 71aaa824ec
commit 82ad8a5df8
9 changed files with 231 additions and 18 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,9 @@
import { HTTP } from '../../vendor/open-api';
import { isIPv4, isIPv6 } from '../../utils';
import { FULL } from '../../utils/logical';
import { getFlag } from '../../utils/geo';
import lodash from 'lodash';
import $ from '../app';
// force to set some properties (e.g., skip-cert-verify, udp, tfo, etc.)
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 ***************************************/
// filter useless proxies
function UselessFilter() {
@ -305,7 +410,7 @@ function TypeFilter(types) {
function filter(proxies) {
return proxies.map(p => {
return p.name.indexOf("🇭🇰") !== -1;
return p.name.indexOf('🇭🇰') !== -1;
});
}
@ -347,6 +452,7 @@ export default {
'Regex Delete Operator': RegexDeleteOperator,
'Script Operator': ScriptOperator,
'Handle Duplicate Operator': HandleDuplicateOperator,
'Resolve Domain Operator': ResolveDomainOperator,
};
async function ApplyFilter(filter, objs) {

View File

@ -2,6 +2,7 @@ export const SETTINGS_KEY = 'settings';
export const SUBS_KEY = 'subs';
export const COLLECTIONS_KEY = 'collections';
export const ARTIFACTS_KEY = 'artifacts';
export const RULES_KEY = 'rules';
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';

View File

@ -3,9 +3,8 @@ import {
GIST_BACKUP_KEY,
GIST_BACKUP_FILE_NAME,
} from './constants';
import { ENV } from '../vendor/open-api';
import { ENV, HTTP } from '../vendor/open-api';
import express from '../vendor/express';
import IP_API from '../utils/ip-api';
import Gist from '../utils/gist';
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);
}

View 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 };

View File

@ -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);
}

File diff suppressed because one or more lines are too long

View 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>

View File

@ -223,6 +223,7 @@ import ScriptFilter from "@/components/ScriptFilter";
import ScriptOperator from "@/components/ScriptOperator";
import RegexSortOperator from "@/components/RegexSortOperator";
import HandleDuplicateOperator from "@/components/HandleDuplicateOperator";
import ResolveDomainOperator from "@/components/ResolveDomainOperator";
const AVAILABLE_PROCESSORS = {
"Flag Operator": {
@ -261,6 +262,10 @@ const AVAILABLE_PROCESSORS = {
component: "HandleDuplicateOperator",
name: "节点去重"
},
"Resolve Domain Operator": {
component: "ResolveDomainOperator",
name: "节点域名解析"
},
"Script Filter": {
component: "ScriptFilter",
name: "脚本过滤器"
@ -291,7 +296,8 @@ export default {
RegexDeleteOperator,
ScriptFilter,
ScriptOperator,
HandleDuplicateOperator
HandleDuplicateOperator,
ResolveDomainOperator
},
data: function () {
return {