mirror of
https://github.com/SukkaW/Surge.git
synced 2025-12-21 13:50:29 +08:00
490 lines
17 KiB
JavaScript
490 lines
17 KiB
JavaScript
'use strict';const fetchRetry=require('./chunks/fetch-retry.D06uBUaW.cjs'),fetchTextByLine=require('./chunks/fetch-text-by-line.YY5b5610.cjs'),require$$0$1=require('node:path'),require$$7=require('@henrygd/queue'),require$$5=require('tldts-experimental'),looseTldtsOpt=require('./chunks/loose-tldts-opt.DLUpGXpj.cjs'),require$$0=require('picocolors'),require$$3$1=require('dns2'),require$$4=require('async-retry'),require$$5$1=require('whoiser'),require$$3=require('foxts/retrie'),require$$1=require('node:process'),require$$5$2=require('fdir');require('undici'),require('undici-cache-store-better-sqlite3'),require('node:util'),require('node:fs'),require('node:readline'),require('node:stream/web'),require('foxts/guard');var validateDomainAlive$1 = {};var isDomainAlive = {};var hasRequiredIsDomainAlive;
|
|
|
|
function requireIsDomainAlive () {
|
|
if (hasRequiredIsDomainAlive) return isDomainAlive;
|
|
hasRequiredIsDomainAlive = 1;
|
|
(function (exports) {
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
function _export(target, all) {
|
|
for(var name in all)Object.defineProperty(target, name, {
|
|
enumerable: true,
|
|
get: all[name]
|
|
});
|
|
}
|
|
_export(exports, {
|
|
isDomainAlive: function() {
|
|
return isDomainAlive;
|
|
},
|
|
keyedAsyncMutexWithQueue: function() {
|
|
return keyedAsyncMutexWithQueue;
|
|
},
|
|
noWhois: function() {
|
|
return noWhois;
|
|
}
|
|
});
|
|
const _tldtsexperimental = /*#__PURE__*/ _interop_require_default(require$$5);
|
|
const _loosetldtsopt = /*@__PURE__*/ looseTldtsOpt.r();
|
|
const _picocolors = /*#__PURE__*/ _interop_require_default(require$$0);
|
|
const _dns2 = /*#__PURE__*/ _interop_require_default(require$$3$1);
|
|
const _asyncretry = /*#__PURE__*/ _interop_require_default(require$$4);
|
|
const _whoiser = /*#__PURE__*/ _interop_require_wildcard(require$$5$1);
|
|
const _retrie = require$$3;
|
|
const _nodeprocess = /*#__PURE__*/ _interop_require_default(require$$1);
|
|
function _interop_require_default(obj) {
|
|
return obj && obj.__esModule ? obj : {
|
|
default: obj
|
|
};
|
|
}
|
|
function _getRequireWildcardCache(nodeInterop) {
|
|
if (typeof WeakMap !== "function") return null;
|
|
var cacheBabelInterop = new WeakMap();
|
|
var cacheNodeInterop = new WeakMap();
|
|
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
})(nodeInterop);
|
|
}
|
|
function _interop_require_wildcard(obj, nodeInterop) {
|
|
if (obj && obj.__esModule) {
|
|
return obj;
|
|
}
|
|
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
return {
|
|
default: obj
|
|
};
|
|
}
|
|
var cache = _getRequireWildcardCache(nodeInterop);
|
|
if (cache && cache.has(obj)) {
|
|
return cache.get(obj);
|
|
}
|
|
var newObj = {
|
|
__proto__: null
|
|
};
|
|
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
for(var key in obj){
|
|
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
if (desc && (desc.get || desc.set)) {
|
|
Object.defineProperty(newObj, key, desc);
|
|
} else {
|
|
newObj[key] = obj[key];
|
|
}
|
|
}
|
|
}
|
|
newObj.default = obj;
|
|
if (cache) {
|
|
cache.set(obj, newObj);
|
|
}
|
|
return newObj;
|
|
}
|
|
const mutex = new Map();
|
|
function keyedAsyncMutexWithQueue(key, fn) {
|
|
if (mutex.has(key)) {
|
|
return mutex.get(key);
|
|
}
|
|
const promise = fn();
|
|
mutex.set(key, promise);
|
|
return promise;
|
|
}
|
|
class DnsError extends Error {
|
|
message;
|
|
server;
|
|
name;
|
|
constructor(message, server){
|
|
super(message), this.message = message, this.server = server, this.name = 'DnsError';
|
|
}
|
|
}
|
|
const dohServers = [
|
|
'8.8.8.8',
|
|
'8.8.4.4',
|
|
'1.0.0.1',
|
|
'1.1.1.1',
|
|
'162.159.36.1',
|
|
'162.159.46.1',
|
|
'101.101.101.101',
|
|
'185.222.222.222',
|
|
'45.11.45.11',
|
|
'dns10.quad9.net',
|
|
'doh.sandbox.opendns.com',
|
|
'unfiltered.adguard-dns.com',
|
|
// '0ms.dev', // Proxy Cloudflare
|
|
// '76.76.2.0', // ControlD unfiltered, path not /dns-query
|
|
// '76.76.10.0', // ControlD unfiltered, path not /dns-query
|
|
// 'dns.bebasid.com', // BebasID, path not /dns-query but /unfiltered
|
|
// '193.110.81.0', // dns0.eu
|
|
// '185.253.5.0', // dns0.eu
|
|
// 'zero.dns0.eu',
|
|
'dns.nextdns.io',
|
|
'anycast.dns.nextdns.io',
|
|
'wikimedia-dns.org',
|
|
// 'ordns.he.net',
|
|
// 'dns.mullvad.net',
|
|
'basic.rethinkdns.com',
|
|
'198.54.117.10' // NameCheap DNS, supports DoT, DoH, UDP53
|
|
].map((dns)=>[
|
|
dns,
|
|
_dns2.default.DOHClient({
|
|
dns,
|
|
http: false
|
|
})
|
|
]);
|
|
const domesticDohServers = [
|
|
'223.5.5.5',
|
|
'223.6.6.6',
|
|
'120.53.53.53',
|
|
'1.12.12.12'
|
|
].map((dns)=>[
|
|
dns,
|
|
_dns2.default.DOHClient({
|
|
dns,
|
|
http: false
|
|
})
|
|
]);
|
|
function createResolve(server) {
|
|
return async (...args)=>{
|
|
try {
|
|
return await (0, _asyncretry.default)(async ()=>{
|
|
const [dohServer, dohClient] = server[Math.floor(Math.random() * server.length)];
|
|
try {
|
|
return {
|
|
...await dohClient(...args),
|
|
dns: dohServer
|
|
};
|
|
} catch (e) {
|
|
// console.error(e);
|
|
throw new DnsError(e.message, dohServer);
|
|
}
|
|
}, {
|
|
retries: 5
|
|
});
|
|
} catch (e) {
|
|
console.log('[doh error]', ...args, e);
|
|
throw e;
|
|
}
|
|
};
|
|
}
|
|
const resolve = createResolve(dohServers);
|
|
const domesticResolve = createResolve(domesticDohServers);
|
|
async function getWhois(domain) {
|
|
return (0, _asyncretry.default)(()=>_whoiser.domain(domain, {
|
|
raw: true
|
|
}), {
|
|
retries: 5
|
|
});
|
|
}
|
|
const domainAliveMap = new Map();
|
|
function onDomainAlive(domain) {
|
|
domainAliveMap.set(domain, true);
|
|
return [
|
|
domain,
|
|
true
|
|
];
|
|
}
|
|
function onDomainDead(domain) {
|
|
domainAliveMap.set(domain, false);
|
|
return [
|
|
domain,
|
|
false
|
|
];
|
|
}
|
|
async function isDomainAlive(domain, isSuffix) {
|
|
if (domainAliveMap.has(domain)) {
|
|
return [
|
|
domain,
|
|
domainAliveMap.get(domain)
|
|
];
|
|
}
|
|
const apexDomain = _tldtsexperimental.default.getDomain(domain, _loosetldtsopt.looseTldtsOpt);
|
|
if (!apexDomain) {
|
|
console.log(_picocolors.default.gray('[domain invalid]'), _picocolors.default.gray('no apex domain'), {
|
|
domain
|
|
});
|
|
return onDomainAlive(domain);
|
|
}
|
|
const apexDomainAlive = await keyedAsyncMutexWithQueue(apexDomain, ()=>isApexDomainAlive(apexDomain));
|
|
if (isSuffix) {
|
|
return apexDomainAlive;
|
|
}
|
|
if (!apexDomainAlive[1]) {
|
|
return apexDomainAlive;
|
|
}
|
|
const $domain = domain[0] === '.' ? domain.slice(1) : domain;
|
|
const aDns = [];
|
|
const aaaaDns = [];
|
|
// test 2 times before make sure record is empty
|
|
for(let i = 0; i < 2; i++){
|
|
// eslint-disable-next-line no-await-in-loop -- sequential
|
|
const aRecords = await resolve($domain, 'A');
|
|
if (aRecords.answers.length > 0) {
|
|
return onDomainAlive(domain);
|
|
}
|
|
aDns.push(aRecords.dns);
|
|
}
|
|
for(let i = 0; i < 2; i++){
|
|
// eslint-disable-next-line no-await-in-loop -- sequential
|
|
const aaaaRecords = await resolve($domain, 'AAAA');
|
|
if (aaaaRecords.answers.length > 0) {
|
|
return onDomainAlive(domain);
|
|
}
|
|
aaaaDns.push(aaaaRecords.dns);
|
|
}
|
|
// only then, let's test once with domesticDohServers
|
|
const aRecords = await domesticResolve($domain, 'A');
|
|
if (aRecords.answers.length > 0) {
|
|
return onDomainAlive(domain);
|
|
}
|
|
aDns.push(aRecords.dns);
|
|
const aaaaRecords = await domesticResolve($domain, 'AAAA');
|
|
if (aaaaRecords.answers.length > 0) {
|
|
return onDomainAlive(domain);
|
|
}
|
|
aaaaDns.push(aaaaRecords.dns);
|
|
console.log(_picocolors.default.red('[domain dead]'), 'no A/AAAA records', {
|
|
domain,
|
|
a: aDns,
|
|
aaaa: aaaaDns
|
|
});
|
|
return onDomainDead($domain);
|
|
}
|
|
const apexDomainNsResolvePromiseMap = new Map();
|
|
async function isApexDomainAlive(apexDomain) {
|
|
if (domainAliveMap.has(apexDomain)) {
|
|
return [
|
|
apexDomain,
|
|
domainAliveMap.get(apexDomain)
|
|
];
|
|
}
|
|
let resp;
|
|
if (apexDomainNsResolvePromiseMap.has(apexDomain)) {
|
|
resp = await apexDomainNsResolvePromiseMap.get(apexDomain);
|
|
} else {
|
|
const promise = resolve(apexDomain, 'NS');
|
|
apexDomainNsResolvePromiseMap.set(apexDomain, promise);
|
|
resp = await promise;
|
|
}
|
|
if (resp.answers.length > 0) {
|
|
return onDomainAlive(apexDomain);
|
|
}
|
|
let whois;
|
|
try {
|
|
whois = await getWhois(apexDomain);
|
|
} catch (e) {
|
|
console.log(_picocolors.default.red('[whois error]'), {
|
|
domain: apexDomain
|
|
}, e);
|
|
return onDomainAlive(apexDomain);
|
|
}
|
|
if (_nodeprocess.default.env.DEBUG) {
|
|
console.log(JSON.stringify(whois, null, 2));
|
|
}
|
|
const whoisError = noWhois(whois);
|
|
if (!whoisError) {
|
|
console.log(_picocolors.default.gray('[domain alive]'), _picocolors.default.gray('whois found'), {
|
|
domain: apexDomain
|
|
});
|
|
return onDomainAlive(apexDomain);
|
|
}
|
|
console.log(_picocolors.default.red('[domain dead]'), 'whois not found', {
|
|
domain: apexDomain,
|
|
err: whoisError
|
|
});
|
|
return onDomainDead(apexDomain);
|
|
}
|
|
// TODO: this is a workaround for https://github.com/LayeredStudio/whoiser/issues/117
|
|
const whoisNotFoundKeywordTest = (0, _retrie.createRetrieKeywordFilter)([
|
|
'no match for',
|
|
'does not exist',
|
|
'not found',
|
|
'no found',
|
|
'no entries',
|
|
'no data found',
|
|
'is available for registration',
|
|
'currently available for application',
|
|
'no matching record',
|
|
'no information available about domain name',
|
|
'not been registered',
|
|
'no match!!',
|
|
'status: available',
|
|
' is free',
|
|
'no object found',
|
|
'nothing found',
|
|
'status: free',
|
|
'pendingdelete',
|
|
' has been blocked by '
|
|
]);
|
|
function noWhois(whois) {
|
|
let empty = true;
|
|
for(const key in whois){
|
|
if (Object.hasOwn(whois, key)) {
|
|
empty = false;
|
|
// if (key === 'error') {
|
|
// // if (
|
|
// // (typeof whois.error === 'string' && whois.error)
|
|
// // || (Array.isArray(whois.error) && whois.error.length > 0)
|
|
// // ) {
|
|
// // console.error(whois);
|
|
// // return true;
|
|
// // }
|
|
// continue;
|
|
// }
|
|
// if (key === 'text') {
|
|
// if (Array.isArray(whois.text)) {
|
|
// for (const value of whois.text) {
|
|
// if (whoisNotFoundKeywordTest(value.toLowerCase())) {
|
|
// return value;
|
|
// }
|
|
// }
|
|
// }
|
|
// continue;
|
|
// }
|
|
// if (key === 'Name Server') {
|
|
// // if (Array.isArray(whois[key]) && whois[key].length === 0) {
|
|
// // return false;
|
|
// // }
|
|
// continue;
|
|
// }
|
|
// if (key === 'Domain Status') {
|
|
// if (Array.isArray(whois[key])) {
|
|
// for (const status of whois[key]) {
|
|
// if (status === 'free' || status === 'AVAILABLE') {
|
|
// return key + ': ' + status;
|
|
// }
|
|
// if (whoisNotFoundKeywordTest(status.toLowerCase())) {
|
|
// return key + ': ' + status;
|
|
// }
|
|
// }
|
|
// }
|
|
// continue;
|
|
// }
|
|
// if (typeof whois[key] === 'string' && whois[key]) {
|
|
// if (whoisNotFoundKeywordTest(whois[key].toLowerCase())) {
|
|
// return key + ': ' + whois[key];
|
|
// }
|
|
// continue;
|
|
// }
|
|
if (key === '__raw' && typeof whois.__raw === 'string') {
|
|
const lines = whois.__raw.trim().toLowerCase().replaceAll(/[\t ]+/g, ' ').split(/\r?\n/);
|
|
if (_nodeprocess.default.env.DEBUG) {
|
|
console.log({
|
|
lines
|
|
});
|
|
}
|
|
for (const line of lines){
|
|
if (whoisNotFoundKeywordTest(line)) {
|
|
return line;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof whois[key] === 'object' && !Array.isArray(whois[key])) {
|
|
const tmp = noWhois(whois[key]);
|
|
if (tmp) {
|
|
return tmp;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (empty) {
|
|
return 'whois is empty';
|
|
}
|
|
return null;
|
|
}
|
|
} (isDomainAlive));
|
|
return isDomainAlive;
|
|
}var hasRequiredValidateDomainAlive;
|
|
|
|
function requireValidateDomainAlive () {
|
|
if (hasRequiredValidateDomainAlive) return validateDomainAlive$1;
|
|
hasRequiredValidateDomainAlive = 1;
|
|
(function (exports) {
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
function _export(target, all) {
|
|
for(var name in all)Object.defineProperty(target, name, {
|
|
enumerable: true,
|
|
get: all[name]
|
|
});
|
|
}
|
|
_export(exports, {
|
|
runAgainstDomainset: function() {
|
|
return runAgainstDomainset;
|
|
},
|
|
runAgainstRuleset: function() {
|
|
return runAgainstRuleset;
|
|
}
|
|
});
|
|
const _fetchtextbyline = /*@__PURE__*/ fetchTextByLine.a();
|
|
const _processline = /*@__PURE__*/ fetchTextByLine.r();
|
|
const _dir = /*@__PURE__*/ fetchRetry.a();
|
|
const _nodepath = /*#__PURE__*/ _interop_require_default(require$$0$1);
|
|
const _queue = require$$7;
|
|
const _isdomainalive = /*@__PURE__*/ requireIsDomainAlive();
|
|
const _fdir = require$$5$2;
|
|
function _interop_require_default(obj) {
|
|
return obj && obj.__esModule ? obj : {
|
|
default: obj
|
|
};
|
|
}
|
|
const queue = (0, _queue.newQueue)(24);
|
|
const deadDomains = [];
|
|
function onDomain(args) {
|
|
if (!args[1]) {
|
|
deadDomains.push(args[0]);
|
|
}
|
|
}
|
|
(async ()=>{
|
|
const domainSets = await new _fdir.fdir().withFullPaths().crawl(_dir.SOURCE_DIR + _nodepath.default.sep + 'domainset').withPromise();
|
|
const domainRules = await new _fdir.fdir().withFullPaths().crawl(_dir.SOURCE_DIR + _nodepath.default.sep + 'non_ip').withPromise();
|
|
await Promise.all([
|
|
...domainSets.map(runAgainstDomainset),
|
|
...domainRules.map(runAgainstRuleset)
|
|
]);
|
|
console.log();
|
|
console.log();
|
|
console.log(JSON.stringify(deadDomains));
|
|
})();
|
|
async function runAgainstRuleset(filepath) {
|
|
const extname = _nodepath.default.extname(filepath);
|
|
if (extname !== '.conf') {
|
|
console.log('[skip]', filepath);
|
|
return;
|
|
}
|
|
const promises = [];
|
|
for await (const l of (0, _fetchtextbyline.readFileByLine)(filepath)){
|
|
const line = (0, _processline.processLine)(l);
|
|
if (!line) continue;
|
|
const [type, domain] = line.split(',');
|
|
switch(type){
|
|
case 'DOMAIN-SUFFIX':
|
|
case 'DOMAIN':
|
|
{
|
|
promises.push(queue.add(()=>(0, _isdomainalive.keyedAsyncMutexWithQueue)(domain, ()=>(0, _isdomainalive.isDomainAlive)(domain, type === 'DOMAIN-SUFFIX'))).then(onDomain));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
console.log('[done]', filepath);
|
|
}
|
|
async function runAgainstDomainset(filepath) {
|
|
const extname = _nodepath.default.extname(filepath);
|
|
if (extname !== '.conf') {
|
|
console.log('[skip]', filepath);
|
|
return;
|
|
}
|
|
const promises = [];
|
|
for await (const l of (0, _fetchtextbyline.readFileByLine)(filepath)){
|
|
const line = (0, _processline.processLine)(l);
|
|
if (!line) continue;
|
|
promises.push(queue.add(()=>(0, _isdomainalive.keyedAsyncMutexWithQueue)(line, ()=>(0, _isdomainalive.isDomainAlive)(line, line[0] === '.'))).then(onDomain));
|
|
}
|
|
await Promise.all(promises);
|
|
console.log('[done]', filepath);
|
|
}
|
|
} (validateDomainAlive$1));
|
|
return validateDomainAlive$1;
|
|
}var validateDomainAliveExports = requireValidateDomainAlive();
|
|
const validateDomainAlive = /*@__PURE__*/fetchRetry.g(validateDomainAliveExports);module.exports=validateDomainAlive; |