mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-13 21:35:55 +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