mirror of
https://github.com/SukkaW/Surge.git
synced 2025-12-12 01:00:34 +08:00
Refactor: full span tracer
This commit is contained in:
parent
0f257e992a
commit
9bb0c14d5f
@ -35,7 +35,7 @@ const getBogusNxDomainIPs = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const buildAntiBogusDomain = task(import.meta.path, async () => {
|
||||
export const buildAntiBogusDomain = task(import.meta.path, async (span) => {
|
||||
const bogusIpPromise = getBogusNxDomainIPs();
|
||||
|
||||
const result: string[] = [];
|
||||
@ -57,7 +57,8 @@ export const buildAntiBogusDomain = task(import.meta.path, async () => {
|
||||
' - https://github.com/felixonmars/dnsmasq-china-list'
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Anti Bogus Domain',
|
||||
description,
|
||||
new Date(),
|
||||
@ -65,7 +66,7 @@ export const buildAntiBogusDomain = task(import.meta.path, async () => {
|
||||
'ruleset',
|
||||
path.resolve(import.meta.dir, '../List/ip/reject.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/ip/reject.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@ -23,7 +23,7 @@ export const getAppleCdnDomainsPromise = createMemoizedPromise(() => traceAsync(
|
||||
picocolors.gray
|
||||
));
|
||||
|
||||
export const buildAppleCdn = task(import.meta.path, async () => {
|
||||
export const buildAppleCdn = task(import.meta.path, async (span) => {
|
||||
const res = await getAppleCdnDomainsPromise();
|
||||
|
||||
const description = [
|
||||
@ -39,7 +39,8 @@ export const buildAppleCdn = task(import.meta.path, async () => {
|
||||
const domainset = res.map(i => `.${i}`);
|
||||
|
||||
return Promise.all([
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Apple CDN',
|
||||
description,
|
||||
new Date(),
|
||||
@ -48,7 +49,8 @@ export const buildAppleCdn = task(import.meta.path, async () => {
|
||||
path.resolve(import.meta.dir, '../List/non_ip/apple_cdn.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/non_ip/apple_cdn.txt')
|
||||
),
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Apple CDN',
|
||||
description,
|
||||
new Date(),
|
||||
|
||||
@ -41,7 +41,7 @@ const getS3OSSDomains = async (): Promise<Set<string>> => {
|
||||
return S3OSSDomains;
|
||||
};
|
||||
|
||||
const buildCdnConf = task(import.meta.path, async () => {
|
||||
const buildCdnConf = task(import.meta.path, async (span) => {
|
||||
/** @type {string[]} */
|
||||
const cdnDomainsList: string[] = [];
|
||||
|
||||
@ -62,7 +62,8 @@ const buildCdnConf = task(import.meta.path, async () => {
|
||||
'This file contains object storage and static assets CDN domains.'
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - CDN Domains',
|
||||
description,
|
||||
new Date(),
|
||||
@ -70,7 +71,7 @@ const buildCdnConf = task(import.meta.path, async () => {
|
||||
'ruleset',
|
||||
path.resolve(import.meta.dir, '../List/non_ip/cdn.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/non_ip/cdn.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
export { buildCdnConf };
|
||||
|
||||
@ -32,7 +32,7 @@ export const getChnCidrPromise = createMemoizedPromise(async () => {
|
||||
);
|
||||
});
|
||||
|
||||
export const buildChnCidr = task(import.meta.path, async () => {
|
||||
export const buildChnCidr = task(import.meta.path, async (span) => {
|
||||
const filteredCidr = await getChnCidrPromise();
|
||||
|
||||
// Can not use SHARED_DESCRIPTION here as different license
|
||||
@ -47,6 +47,7 @@ export const buildChnCidr = task(import.meta.path, async () => {
|
||||
// Can not use createRuleset here, as Clash support advanced ipset syntax
|
||||
return Promise.all([
|
||||
compareAndWriteFile(
|
||||
span,
|
||||
withBannerArray(
|
||||
'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
|
||||
description,
|
||||
@ -56,6 +57,7 @@ export const buildChnCidr = task(import.meta.path, async () => {
|
||||
pathResolve(import.meta.dir, '../List/ip/china_ip.conf')
|
||||
),
|
||||
compareAndWriteFile(
|
||||
span,
|
||||
withBannerArray(
|
||||
'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
|
||||
description,
|
||||
|
||||
@ -7,7 +7,7 @@ import { task } from './trace';
|
||||
const outputSurgeDir = path.resolve(import.meta.dir, '../List');
|
||||
const outputClashDir = path.resolve(import.meta.dir, '../Clash');
|
||||
|
||||
export const buildCloudMounterRules = task(import.meta.path, async () => {
|
||||
export const buildCloudMounterRules = task(import.meta.path, async (span) => {
|
||||
// AND,((SRC-IP,192.168.1.110), (DOMAIN, example.com))
|
||||
|
||||
const results = DOMAINS.flatMap(domain => {
|
||||
@ -18,7 +18,8 @@ export const buildCloudMounterRules = task(import.meta.path, async () => {
|
||||
|
||||
const description = SHARED_DESCRIPTION;
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - CloudMounter / RaiDrive',
|
||||
description,
|
||||
new Date(),
|
||||
@ -26,7 +27,7 @@ export const buildCloudMounterRules = task(import.meta.path, async () => {
|
||||
'domainset',
|
||||
path.resolve(outputSurgeDir, 'non_ip', 'cloudmounter.conf'),
|
||||
path.resolve(outputClashDir, 'non_ip', 'cloudmounter.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@ -6,6 +6,7 @@ import { readFileByLine } from './lib/fetch-text-by-line';
|
||||
import { processLine } from './lib/process-line';
|
||||
import { createRuleset } from './lib/create-file';
|
||||
import { domainDeduper } from './lib/domain-deduper';
|
||||
import type { Span } from './trace';
|
||||
import { task } from './trace';
|
||||
import { SHARED_DESCRIPTION } from './lib/constants';
|
||||
|
||||
@ -17,7 +18,7 @@ const sourceDir = path.resolve(import.meta.dir, '../Source');
|
||||
const outputSurgeDir = path.resolve(import.meta.dir, '../List');
|
||||
const outputClashDir = path.resolve(import.meta.dir, '../Clash');
|
||||
|
||||
export const buildCommon = task(import.meta.path, async () => {
|
||||
export const buildCommon = task(import.meta.path, async (span) => {
|
||||
const promises: Array<Promise<unknown>> = [];
|
||||
|
||||
const pw = new PathScurry(sourceDir);
|
||||
@ -33,14 +34,14 @@ export const buildCommon = task(import.meta.path, async () => {
|
||||
|
||||
const relativePath = entry.relative();
|
||||
if (relativePath.startsWith('domainset/')) {
|
||||
promises.push(transformDomainset(entry.fullpath(), relativePath));
|
||||
promises.push(transformDomainset(span, entry.fullpath(), relativePath));
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
relativePath.startsWith('ip/')
|
||||
|| relativePath.startsWith('non_ip/')
|
||||
) {
|
||||
promises.push(transformRuleset(entry.fullpath(), relativePath));
|
||||
promises.push(transformRuleset(span, entry.fullpath(), relativePath));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -52,46 +53,50 @@ if (import.meta.main) {
|
||||
buildCommon();
|
||||
}
|
||||
|
||||
const processFile = async (sourcePath: string) => {
|
||||
console.log('Processing', sourcePath);
|
||||
const processFile = (span: Span, sourcePath: string) => {
|
||||
// console.log('Processing', sourcePath);
|
||||
return span.traceChild(`process file: ${sourcePath}`).traceAsyncFn(async () => {
|
||||
const lines: string[] = [];
|
||||
|
||||
const lines: string[] = [];
|
||||
let title = '';
|
||||
const descriptions: string[] = [];
|
||||
|
||||
let title = '';
|
||||
const descriptions: string[] = [];
|
||||
try {
|
||||
for await (const line of readFileByLine(sourcePath)) {
|
||||
if (line === MAGIC_COMMAND_SKIP) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
for await (const line of readFileByLine(sourcePath)) {
|
||||
if (line === MAGIC_COMMAND_SKIP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.startsWith(MAGIC_COMMAND_TITLE)) {
|
||||
title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
|
||||
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
const l = processLine(line);
|
||||
if (l) {
|
||||
lines.push(l);
|
||||
if (line.startsWith(MAGIC_COMMAND_TITLE)) {
|
||||
title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
|
||||
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
const l = processLine(line);
|
||||
if (l) {
|
||||
lines.push(l);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error processing', sourcePath);
|
||||
console.trace(e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error processing', sourcePath);
|
||||
console.trace(e);
|
||||
}
|
||||
|
||||
return [title, descriptions, lines] as const;
|
||||
return [title, descriptions, lines] as const;
|
||||
});
|
||||
};
|
||||
|
||||
async function transformDomainset(sourcePath: string, relativePath: string) {
|
||||
const res = await processFile(sourcePath);
|
||||
async function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) {
|
||||
const span = parentSpan.traceChild(`transform domainset: ${path.basename(sourcePath, path.extname(sourcePath))}`);
|
||||
|
||||
const res = await processFile(span, sourcePath);
|
||||
if (!res) return;
|
||||
|
||||
const [title, descriptions, lines] = res;
|
||||
|
||||
const deduped = domainDeduper(lines);
|
||||
@ -104,7 +109,8 @@ async function transformDomainset(sourcePath: string, relativePath: string) {
|
||||
)
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
title,
|
||||
description,
|
||||
new Date(),
|
||||
@ -112,15 +118,18 @@ async function transformDomainset(sourcePath: string, relativePath: string) {
|
||||
'domainset',
|
||||
path.resolve(outputSurgeDir, relativePath),
|
||||
path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output Surge RULE-SET and Clash classical text format
|
||||
*/
|
||||
async function transformRuleset(sourcePath: string, relativePath: string) {
|
||||
const res = await processFile(sourcePath);
|
||||
if (!res) return;
|
||||
async function transformRuleset(parentSpan: Span, sourcePath: string, relativePath: string) {
|
||||
const span = parentSpan.traceChild(`transform ruleset: ${path.basename(sourcePath, path.extname(sourcePath))}`);
|
||||
|
||||
const res = await processFile(span, sourcePath);
|
||||
if (!res) return null;
|
||||
|
||||
const [title, descriptions, lines] = res;
|
||||
|
||||
const description = [
|
||||
@ -132,7 +141,8 @@ async function transformRuleset(sourcePath: string, relativePath: string) {
|
||||
)
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
title,
|
||||
description,
|
||||
new Date(),
|
||||
@ -140,5 +150,5 @@ async function transformRuleset(sourcePath: string, relativePath: string) {
|
||||
'ruleset',
|
||||
path.resolve(outputSurgeDir, relativePath),
|
||||
path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ export const getDomesticDomainsRulesetPromise = createMemoizedPromise(async () =
|
||||
return results;
|
||||
});
|
||||
|
||||
export const buildDomesticRuleset = task(import.meta.path, async () => {
|
||||
export const buildDomesticRuleset = task(import.meta.path, async (span) => {
|
||||
const rulesetDescription = [
|
||||
...SHARED_DESCRIPTION,
|
||||
'',
|
||||
@ -29,7 +29,8 @@ export const buildDomesticRuleset = task(import.meta.path, async () => {
|
||||
];
|
||||
|
||||
return Promise.all([
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Domestic Domains',
|
||||
rulesetDescription,
|
||||
new Date(),
|
||||
@ -39,6 +40,7 @@ export const buildDomesticRuleset = task(import.meta.path, async () => {
|
||||
path.resolve(import.meta.dir, '../Clash/non_ip/domestic.txt')
|
||||
),
|
||||
compareAndWriteFile(
|
||||
span,
|
||||
[
|
||||
'#!name=[Sukka] Local DNS Mapping',
|
||||
`#!desc=Last Updated: ${new Date().toISOString()}`,
|
||||
|
||||
@ -33,7 +33,7 @@ const processLocalRuleSet = async (ruleSetPath: string, set: Set<string>, keywor
|
||||
}
|
||||
};
|
||||
|
||||
export const buildInternalCDNDomains = task(import.meta.path, async () => {
|
||||
export const buildInternalCDNDomains = task(import.meta.path, async (span) => {
|
||||
const proxySet = new Set<string>();
|
||||
const proxyKeywords = new Set<string>();
|
||||
|
||||
@ -50,6 +50,7 @@ export const buildInternalCDNDomains = task(import.meta.path, async () => {
|
||||
]))[0];
|
||||
|
||||
return compareAndWriteFile(
|
||||
span,
|
||||
[
|
||||
...sortDomains(Array.from(proxySet), gorhill).map(i => `SUFFIX,${i}`),
|
||||
...Array.from(proxyKeywords).sort().map(i => `REGEX,${i}`)
|
||||
|
||||
@ -41,7 +41,7 @@ export const getMicrosoftCdnRulesetPromise = createMemoizedPromise(async () => {
|
||||
return Array.from(set).map(d => `DOMAIN-SUFFIX,${d}`).concat(WHITELIST);
|
||||
});
|
||||
|
||||
export const buildMicrosoftCdn = task(import.meta.path, async () => {
|
||||
export const buildMicrosoftCdn = task(import.meta.path, async (span) => {
|
||||
const description = [
|
||||
...SHARED_DESCRIPTION,
|
||||
'',
|
||||
@ -51,7 +51,8 @@ export const buildMicrosoftCdn = task(import.meta.path, async () => {
|
||||
' - https://github.com/felixonmars/dnsmasq-china-list'
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Microsoft CDN',
|
||||
description,
|
||||
new Date(),
|
||||
@ -59,7 +60,7 @@ export const buildMicrosoftCdn = task(import.meta.path, async () => {
|
||||
'ruleset',
|
||||
path.resolve(import.meta.dir, '../List/non_ip/microsoft_cdn.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/non_ip/microsoft_cdn.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@ -29,20 +29,20 @@ export const buildRejectDomainSet = task(import.meta.path, async (span) => {
|
||||
// Parse from AdGuard Filters
|
||||
const [gorhill, shouldStop] = await span
|
||||
.traceChild('download and process hosts / adblock filter rules')
|
||||
.traceAsyncFn(async () => {
|
||||
.traceAsyncFn(async (childSpan) => {
|
||||
let shouldStop = false;
|
||||
|
||||
const [gorhill] = await Promise.all([
|
||||
getGorhillPublicSuffixPromise(),
|
||||
// Parse from remote hosts & domain lists
|
||||
...HOSTS.map(entry => processHosts(span, entry[0], entry[1], entry[2]).then(hosts => {
|
||||
...HOSTS.map(entry => processHosts(childSpan, entry[0], entry[1], entry[2]).then(hosts => {
|
||||
SetHelpers.add(domainSets, hosts);
|
||||
})),
|
||||
...DOMAIN_LISTS.map(entry => processDomainLists(span, entry[0], entry[1], entry[2])),
|
||||
...DOMAIN_LISTS.map(entry => processDomainLists(childSpan, entry[0], entry[1], entry[2])),
|
||||
...ADGUARD_FILTERS.map(input => {
|
||||
const promise = typeof input === 'string'
|
||||
? processFilterRules(span, input)
|
||||
: processFilterRules(span, input[0], input[1], input[2]);
|
||||
? processFilterRules(childSpan, input)
|
||||
: processFilterRules(childSpan, input[0], input[1], input[2]);
|
||||
|
||||
return promise.then(({ white, black, foundDebugDomain }) => {
|
||||
if (foundDebugDomain) {
|
||||
@ -56,22 +56,22 @@ export const buildRejectDomainSet = task(import.meta.path, async (span) => {
|
||||
...([
|
||||
'https://raw.githubusercontent.com/AdguardTeam/AdGuardSDNSFilter/master/Filters/exceptions.txt',
|
||||
'https://raw.githubusercontent.com/AdguardTeam/AdGuardSDNSFilter/master/Filters/exclusions.txt'
|
||||
].map(input => processFilterRules(span, input).then(({ white, black }) => {
|
||||
].map(input => processFilterRules(childSpan, input).then(({ white, black }) => {
|
||||
setAddFromArray(filterRuleWhitelistDomainSets, white);
|
||||
setAddFromArray(filterRuleWhitelistDomainSets, black);
|
||||
}))),
|
||||
getPhishingDomains(span).then(([purePhishingDomains, fullPhishingDomainSet]) => {
|
||||
getPhishingDomains(childSpan).then(([purePhishingDomains, fullPhishingDomainSet]) => {
|
||||
SetHelpers.add(domainSets, fullPhishingDomainSet);
|
||||
setAddFromArray(domainSets, purePhishingDomains);
|
||||
}),
|
||||
(async () => {
|
||||
childSpan.traceChild('process reject_sukka.conf').traceAsyncFn(async () => {
|
||||
for await (const l of readFileByLine(path.resolve(import.meta.dir, '../Source/domainset/reject_sukka.conf'))) {
|
||||
const line = processLine(l);
|
||||
if (line) {
|
||||
domainSets.add(line);
|
||||
}
|
||||
}
|
||||
})()
|
||||
})
|
||||
]);
|
||||
|
||||
// remove pre-defined enforced blacklist from whitelist
|
||||
@ -187,7 +187,8 @@ export const buildRejectDomainSet = task(import.meta.path, async (span) => {
|
||||
];
|
||||
|
||||
return Promise.all([
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Reject Base',
|
||||
description,
|
||||
new Date(),
|
||||
@ -197,6 +198,7 @@ export const buildRejectDomainSet = task(import.meta.path, async (span) => {
|
||||
path.resolve(import.meta.dir, '../Clash/domainset/reject.txt')
|
||||
),
|
||||
compareAndWriteFile(
|
||||
span,
|
||||
rejectDomainsStats.map(([domain, count]) => `${domain}${' '.repeat(100 - domain.length)}${count}`),
|
||||
path.resolve(import.meta.dir, '../List/internal/reject-stats.txt')
|
||||
),
|
||||
|
||||
@ -38,8 +38,9 @@ const HOSTNAMES = [
|
||||
'GC._msDCS.*.*'
|
||||
] as const;
|
||||
|
||||
export const buildAlwaysRealIPModule = task(import.meta.path, async () => {
|
||||
export const buildAlwaysRealIPModule = task(import.meta.path, async (span) => {
|
||||
return compareAndWriteFile(
|
||||
span,
|
||||
[
|
||||
'#!name=[Sukka] Always Real IP Plus',
|
||||
`#!desc=Last Updated: ${new Date().toISOString()}`,
|
||||
|
||||
@ -71,10 +71,11 @@ const REDIRECT = [
|
||||
['googleajax.wp-china-yes.net/', 'https://ajax.googleapis.com/']
|
||||
] as const;
|
||||
|
||||
export const buildRedirectModule = task(import.meta.path, async () => {
|
||||
export const buildRedirectModule = task(import.meta.path, async (span) => {
|
||||
const domains = Array.from(new Set(REDIRECT.map(([from]) => tldts.getHostname(from, { detectIp: false })))).filter(Boolean);
|
||||
|
||||
return compareAndWriteFile(
|
||||
span,
|
||||
[
|
||||
'#!name=[Sukka] URL Redirect',
|
||||
`#!desc=Last Updated: ${new Date().toISOString()}`,
|
||||
|
||||
@ -151,13 +151,14 @@ export const buildSpeedtestDomainSet = task(import.meta.path, async (span) => {
|
||||
'.backend.librespeed.org'
|
||||
]);
|
||||
|
||||
// Download previous speedtest domainset
|
||||
for await (const l of await fetchRemoteTextByLine('https://ruleset.skk.moe/List/domainset/speedtest.conf')) {
|
||||
const line = processLine(l);
|
||||
if (line) {
|
||||
domains.add(line);
|
||||
await span.traceChild('fetch previous speedtest domainset').traceAsyncFn(async () => {
|
||||
for await (const l of await fetchRemoteTextByLine('https://ruleset.skk.moe/List/domainset/speedtest.conf')) {
|
||||
const line = processLine(l);
|
||||
if (line) {
|
||||
domains.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const pMap = ([
|
||||
@ -225,7 +226,8 @@ export const buildSpeedtestDomainSet = task(import.meta.path, async (span) => {
|
||||
'This file contains common speedtest endpoints.'
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Speedtest Domains',
|
||||
description,
|
||||
new Date(),
|
||||
@ -233,7 +235,7 @@ export const buildSpeedtestDomainSet = task(import.meta.path, async (span) => {
|
||||
'domainset',
|
||||
path.resolve(import.meta.dir, '../List/domainset/speedtest.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/domainset/speedtest.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@ -26,7 +26,7 @@ const removeNoResolved = (line: string) => line.replace(',no-resolve', '');
|
||||
/**
|
||||
* This only generates a simplified version, for under-used users only.
|
||||
*/
|
||||
export const buildSSPanelUIMAppProfile = task(import.meta.path, async () => {
|
||||
export const buildSSPanelUIMAppProfile = task(import.meta.path, async (span) => {
|
||||
const [
|
||||
domesticDomains,
|
||||
appleCdnDomains,
|
||||
@ -108,6 +108,7 @@ export const buildSSPanelUIMAppProfile = task(import.meta.path, async () => {
|
||||
);
|
||||
|
||||
await compareAndWriteFile(
|
||||
span,
|
||||
output,
|
||||
path.resolve(import.meta.dir, '../List/internal/appprofile.php')
|
||||
);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// @ts-check
|
||||
import type { Span } from './trace';
|
||||
import { task } from './trace';
|
||||
|
||||
import path from 'path';
|
||||
@ -7,10 +8,12 @@ import { createRuleset } from './lib/create-file';
|
||||
import { ALL, NORTH_AMERICA, EU, HK, TW, JP, KR } from '../Source/stream';
|
||||
import { SHARED_DESCRIPTION } from './lib/constants';
|
||||
|
||||
export const createRulesetForStreamService = (fileId: string, title: string, streamServices: Array<import('../Source/stream').StreamService>) => {
|
||||
export const createRulesetForStreamService = (span: Span, fileId: string, title: string, streamServices: Array<import('../Source/stream').StreamService>) => {
|
||||
const childSpan = span.traceChild(fileId);
|
||||
return [
|
||||
// Domains
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
childSpan,
|
||||
`Sukka's Ruleset - Stream Services: ${title}`,
|
||||
[
|
||||
...SHARED_DESCRIPTION,
|
||||
@ -24,7 +27,8 @@ export const createRulesetForStreamService = (fileId: string, title: string, str
|
||||
path.resolve(import.meta.dir, `../Clash/non_ip/${fileId}.txt`)
|
||||
),
|
||||
// IP
|
||||
...createRuleset(
|
||||
createRuleset(
|
||||
childSpan,
|
||||
`Sukka's Ruleset - Stream Services' IPs: ${title}`,
|
||||
[
|
||||
...SHARED_DESCRIPTION,
|
||||
@ -47,16 +51,16 @@ export const createRulesetForStreamService = (fileId: string, title: string, str
|
||||
];
|
||||
};
|
||||
|
||||
export const buildStreamService = task(import.meta.path, async () => {
|
||||
export const buildStreamService = task(import.meta.path, async (span) => {
|
||||
return Promise.all([
|
||||
...createRulesetForStreamService('stream', 'All', ALL),
|
||||
...createRulesetForStreamService('stream_us', 'North America', NORTH_AMERICA),
|
||||
...createRulesetForStreamService('stream_eu', 'Europe', EU),
|
||||
...createRulesetForStreamService('stream_hk', 'Hong Kong', HK),
|
||||
...createRulesetForStreamService('stream_tw', 'Taiwan', TW),
|
||||
...createRulesetForStreamService('stream_jp', 'Japan', JP),
|
||||
...createRulesetForStreamService(span, 'stream', 'All', ALL),
|
||||
...createRulesetForStreamService(span, 'stream_us', 'North America', NORTH_AMERICA),
|
||||
...createRulesetForStreamService(span, 'stream_eu', 'Europe', EU),
|
||||
...createRulesetForStreamService(span, 'stream_hk', 'Hong Kong', HK),
|
||||
...createRulesetForStreamService(span, 'stream_tw', 'Taiwan', TW),
|
||||
...createRulesetForStreamService(span, 'stream_jp', 'Japan', JP),
|
||||
// ...createRulesetForStreamService('stream_au', 'Oceania', AU),
|
||||
...createRulesetForStreamService('stream_kr', 'Korean', KR)
|
||||
...createRulesetForStreamService(span, 'stream_kr', 'Korean', KR)
|
||||
// ...createRulesetForStreamService('stream_south_east_asia', 'South East Asia', SOUTH_EAST_ASIA)
|
||||
]);
|
||||
});
|
||||
|
||||
@ -32,7 +32,7 @@ export const getTelegramCIDRPromise = createMemoizedPromise(async () => {
|
||||
return { date, results };
|
||||
});
|
||||
|
||||
export const buildTelegramCIDR = task(import.meta.path, async () => {
|
||||
export const buildTelegramCIDR = task(import.meta.path, async (span) => {
|
||||
const { date, results } = await getTelegramCIDRPromise();
|
||||
|
||||
if (results.length === 0) {
|
||||
@ -45,7 +45,8 @@ export const buildTelegramCIDR = task(import.meta.path, async () => {
|
||||
' - https://core.telegram.org/resources/cidr.txt'
|
||||
];
|
||||
|
||||
return Promise.all(createRuleset(
|
||||
return createRuleset(
|
||||
span,
|
||||
'Sukka\'s Ruleset - Telegram IP CIDR',
|
||||
description,
|
||||
date,
|
||||
@ -53,7 +54,7 @@ export const buildTelegramCIDR = task(import.meta.path, async () => {
|
||||
'ruleset',
|
||||
path.resolve(import.meta.dir, '../List/ip/telegram.conf'),
|
||||
path.resolve(import.meta.dir, '../Clash/ip/telegram.txt')
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
if (import.meta.main) {
|
||||
|
||||
@ -12,15 +12,11 @@ const ASSETS_LIST = {
|
||||
|
||||
const mockDir = path.resolve(import.meta.dir, '../Mock');
|
||||
|
||||
export const downloadMockAssets = task(import.meta.path, () => Promise.all(Object.entries(ASSETS_LIST).map(async ([filename, url]) => {
|
||||
const targetPath = path.join(mockDir, filename);
|
||||
|
||||
const key = picocolors.gray(`Download ${filename}`);
|
||||
console.time(key);
|
||||
const res = await fetchWithRetry(url);
|
||||
await Bun.write(targetPath, res);
|
||||
console.timeEnd(key);
|
||||
})));
|
||||
export const downloadMockAssets = task(import.meta.path, (span) => Promise.all(Object.entries(ASSETS_LIST).map(
|
||||
([filename, url]) => span
|
||||
.traceChild(url)
|
||||
.traceAsyncFn(() => fetchWithRetry(url).then(res => Bun.write(path.join(mockDir, filename), res)))
|
||||
)));
|
||||
|
||||
if (import.meta.main) {
|
||||
downloadMockAssets();
|
||||
|
||||
@ -3,8 +3,10 @@ import { readFileByLine } from './fetch-text-by-line';
|
||||
import { surgeDomainsetToClashDomainset, surgeRulesetToClashClassicalTextRuleset } from './clash';
|
||||
import { traceAsync } from './trace-runner';
|
||||
import picocolors from 'picocolors';
|
||||
import type { Span } from '../trace';
|
||||
import path from 'path';
|
||||
|
||||
export async function compareAndWriteFile(linesA: string[], filePath: string) {
|
||||
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
|
||||
let isEqual = true;
|
||||
const file = Bun.file(filePath);
|
||||
|
||||
@ -17,48 +19,44 @@ export async function compareAndWriteFile(linesA: string[], filePath: string) {
|
||||
console.log(`Nothing to write to ${filePath}...`);
|
||||
isEqual = false;
|
||||
} else {
|
||||
isEqual = await traceAsync(
|
||||
picocolors.gray(`comparing ${filePath}`),
|
||||
async () => {
|
||||
let index = 0;
|
||||
isEqual = await span.traceChild(`comparing ${filePath}`).traceAsyncFn(async () => {
|
||||
let index = 0;
|
||||
|
||||
for await (const lineB of readFileByLine(file)) {
|
||||
const lineA = linesA[index];
|
||||
index++;
|
||||
for await (const lineB of readFileByLine(file)) {
|
||||
const lineA = linesA[index];
|
||||
index++;
|
||||
|
||||
if (lineA == null) {
|
||||
// The file becomes smaller
|
||||
return false;
|
||||
}
|
||||
if (lineA == null) {
|
||||
// The file becomes smaller
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lineA[0] === '#' && lineB[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
lineA[0] === '/'
|
||||
if (lineA[0] === '#' && lineB[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
lineA[0] === '/'
|
||||
&& lineA[1] === '/'
|
||||
&& lineA[3] === '#'
|
||||
&& lineB[0] === '/'
|
||||
&& lineB[1] === '/'
|
||||
&& lineB[3] === '#'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lineA !== lineB) {
|
||||
return false;
|
||||
}
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index !== linesALen) {
|
||||
// The file becomes larger
|
||||
if (lineA !== lineB) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
picocolors.gray
|
||||
);
|
||||
if (index !== linesALen) {
|
||||
// The file becomes larger
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (isEqual) {
|
||||
@ -66,7 +64,7 @@ export async function compareAndWriteFile(linesA: string[], filePath: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
await traceAsync(picocolors.gray(`writing ${filePath}`), async () => {
|
||||
await span.traceChild(`writing ${filePath}`).traceAsyncFn(async () => {
|
||||
if (linesALen < 10000) {
|
||||
return Bun.write(file, `${linesA.join('\n')}\n`);
|
||||
}
|
||||
@ -79,7 +77,7 @@ export async function compareAndWriteFile(linesA: string[], filePath: string) {
|
||||
}
|
||||
|
||||
return writer.end();
|
||||
}, picocolors.gray);
|
||||
});
|
||||
}
|
||||
|
||||
export const withBannerArray = (title: string, description: string[] | readonly string[], date: Date, content: string[]) => {
|
||||
@ -96,27 +94,28 @@ export const withBannerArray = (title: string, description: string[] | readonly
|
||||
};
|
||||
|
||||
export const createRuleset = (
|
||||
parentSpan: Span,
|
||||
title: string, description: string[] | readonly string[], date: Date, content: string[],
|
||||
type: 'ruleset' | 'domainset', surgePath: string, clashPath: string
|
||||
) => {
|
||||
) => parentSpan.traceChild(`create ruleset: ${path.basename(surgePath, path.extname(surgePath))}`).traceAsyncFn((childSpan) => {
|
||||
const surgeContent = withBannerArray(title, description, date, content);
|
||||
const clashContent = childSpan.traceChild('convert incoming ruleset to clash').traceSyncFn(() => {
|
||||
let _clashContent;
|
||||
switch (type) {
|
||||
case 'domainset':
|
||||
_clashContent = surgeDomainsetToClashDomainset(content);
|
||||
break;
|
||||
case 'ruleset':
|
||||
_clashContent = surgeRulesetToClashClassicalTextRuleset(content);
|
||||
break;
|
||||
default:
|
||||
throw new TypeError(`Unknown type: ${type as any}`);
|
||||
}
|
||||
return withBannerArray(title, description, date, _clashContent);
|
||||
});
|
||||
|
||||
let _clashContent;
|
||||
switch (type) {
|
||||
case 'domainset':
|
||||
_clashContent = surgeDomainsetToClashDomainset(content);
|
||||
break;
|
||||
case 'ruleset':
|
||||
_clashContent = surgeRulesetToClashClassicalTextRuleset(content);
|
||||
break;
|
||||
default:
|
||||
throw new TypeError(`Unknown type: ${type as any}`);
|
||||
}
|
||||
|
||||
const clashContent = withBannerArray(title, description, date, _clashContent);
|
||||
|
||||
return [
|
||||
compareAndWriteFile(surgeContent, surgePath),
|
||||
compareAndWriteFile(clashContent, clashPath)
|
||||
];
|
||||
};
|
||||
return Promise.all([
|
||||
compareAndWriteFile(childSpan, surgeContent, surgePath),
|
||||
compareAndWriteFile(childSpan, clashContent, clashPath)
|
||||
]);
|
||||
});
|
||||
|
||||
@ -41,17 +41,16 @@ export function readFileByLine(file: string | URL | BunFile) {
|
||||
return createTextLineAsyncGeneratorFromStreamSource(file.stream());
|
||||
}
|
||||
|
||||
export function createReadlineInterfaceFromResponse(resp: Response) {
|
||||
export function createReadlineInterfaceFromResponse(this: void, resp: Response) {
|
||||
if (!resp.body) {
|
||||
throw new Error('Failed to fetch remote text');
|
||||
}
|
||||
if (resp.bodyUsed) {
|
||||
throw new Error('Body has already been consumed.');
|
||||
}
|
||||
|
||||
return createTextLineAsyncGeneratorFromStreamSource(resp.body);
|
||||
}
|
||||
|
||||
export function fetchRemoteTextByLine(url: string | URL) {
|
||||
return fetchWithRetry(url, defaultRequestInit).then(res => createReadlineInterfaceFromResponse(res));
|
||||
return fetchWithRetry(url, defaultRequestInit).then(createReadlineInterfaceFromResponse);
|
||||
}
|
||||
|
||||
@ -96,12 +96,12 @@ const enum ParseType {
|
||||
}
|
||||
|
||||
export async function processFilterRules(
|
||||
span: Span,
|
||||
parentSpan: Span,
|
||||
filterRulesUrl: string,
|
||||
fallbackUrls?: readonly string[] | undefined | null,
|
||||
ttl: number | null = null
|
||||
): Promise<{ white: string[], black: string[], foundDebugDomain: boolean }> {
|
||||
const [white, black, warningMessages] = await span.traceChild('process filter rules: domainListsUrl').traceAsyncFn(() => fsCache.apply<Readonly<[
|
||||
const [white, black, warningMessages] = await parentSpan.traceChild(`process filter rules: ${filterRulesUrl}`).traceAsyncFn((span) => fsCache.apply<Readonly<[
|
||||
white: string[],
|
||||
black: string[],
|
||||
warningMessages: string[]
|
||||
@ -179,18 +179,15 @@ export async function processFilterRules(
|
||||
// Avoid event loop starvation, so we wait for a macrotask before we start fetching.
|
||||
await Promise.resolve();
|
||||
|
||||
const filterRules = (await traceAsync(
|
||||
picocolors.gray(`- download ${filterRulesUrl}`),
|
||||
() => fetchAssets(filterRulesUrl, fallbackUrls),
|
||||
picocolors.gray
|
||||
)).split('\n');
|
||||
const filterRules = await span.traceChild('download adguard filter').traceAsyncFn(() => {
|
||||
return fetchAssets(filterRulesUrl, fallbackUrls).then(text => text.split('\n'));
|
||||
});
|
||||
|
||||
const key = picocolors.gray(`- parse adguard filter ${filterRulesUrl}`);
|
||||
console.time(key);
|
||||
for (let i = 0, len = filterRules.length; i < len; i++) {
|
||||
lineCb(filterRules[i]);
|
||||
}
|
||||
console.timeEnd(key);
|
||||
span.traceChild('parse adguard filter').traceSyncFn(() => {
|
||||
for (let i = 0, len = filterRules.length; i < len; i++) {
|
||||
lineCb(filterRules[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
@ -47,7 +47,9 @@ export const DOMAIN_LISTS = [
|
||||
['https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/data/combined_disguised_mail_trackers_justdomains.txt', true, TTL.THREE_DAYS()]
|
||||
] as const;
|
||||
|
||||
export const ADGUARD_FILTERS = [
|
||||
type AdGuardFilterSource = string | [main: string, mirrors: string[] | null, ttl: number];
|
||||
|
||||
export const ADGUARD_FILTERS: AdGuardFilterSource[] = [
|
||||
// EasyList
|
||||
[
|
||||
'https://easylist.to/easylist/easylist.txt',
|
||||
@ -156,7 +158,7 @@ export const ADGUARD_FILTERS = [
|
||||
// Not actively maintained, let's use a 10 days cache ttl
|
||||
['https://raw.githubusercontent.com/Spam404/lists/master/adblock-list.txt', null, TTL.TEN_DAYS()],
|
||||
// Brave First Party & First Party CNAME
|
||||
'https://raw.githubusercontent.com/brave/adblock-lists/master/brave-lists/brave-firstparty.txt'
|
||||
['https://raw.githubusercontent.com/brave/adblock-lists/master/brave-lists/brave-firstparty.txt', null, TTL.ONE_DAY()]
|
||||
] as const;
|
||||
|
||||
export const PREDEFINED_WHITELIST = [
|
||||
|
||||
@ -114,17 +114,45 @@ export const universalify = <A extends any[], R>(taskname: string, fn: (this: vo
|
||||
};
|
||||
};
|
||||
|
||||
export const printTraceResult = (traceResult: TraceResult = rootTraceResult, level = 0, isLast = false) => {
|
||||
if (level === 0) {
|
||||
printStats(traceResult.children);
|
||||
export const printTraceResult = (traceResult: TraceResult = rootTraceResult) => {
|
||||
printStats(traceResult.children);
|
||||
printTree(traceResult, node => `${node.name} ${picocolors.bold(`${(node.end - node.start).toFixed(3)}ms`)}`);
|
||||
};
|
||||
|
||||
function printTree(initialTree: TraceResult, printNode: (node: TraceResult, branch: string) => string) {
|
||||
function printBranch(tree: TraceResult, branch: string) {
|
||||
const isGraphHead = branch.length === 0;
|
||||
const children = tree.children;
|
||||
|
||||
let branchHead = '';
|
||||
|
||||
if (!isGraphHead) {
|
||||
branchHead = children.length > 0 ? '┬ ' : '─ ';
|
||||
}
|
||||
|
||||
const toPrint = printNode(tree, `${branch}${branchHead}`);
|
||||
|
||||
if (typeof toPrint === 'string') {
|
||||
console.log(`${branch}${branchHead}${toPrint}`);
|
||||
}
|
||||
|
||||
let baseBranch = branch;
|
||||
|
||||
if (!isGraphHead) {
|
||||
const isChildOfLastBranch = branch.endsWith('└─');
|
||||
baseBranch = branch.slice(0, -2) + (isChildOfLastBranch ? ' ' : '│ ');
|
||||
}
|
||||
|
||||
const nextBranch = `${baseBranch}├─`;
|
||||
const lastBranch = `${baseBranch}└─`;
|
||||
|
||||
children.forEach((child, index) => {
|
||||
printBranch(child, children.length - 1 === index ? lastBranch : nextBranch);
|
||||
});
|
||||
}
|
||||
|
||||
const prefix = (level > 0 ? ` ${'│ '.repeat(level - 1)}` : '') + (level > 0 ? (isLast ? '└─' : '├─') : '');
|
||||
|
||||
console.log(`${prefix} ${traceResult.name} ${picocolors.bold(`${(traceResult.end - traceResult.start).toFixed(2)}ms`)}`);
|
||||
|
||||
traceResult.children.forEach((child, index, arr) => printTraceResult(child, level + 1, index === arr.length - 1));
|
||||
};
|
||||
printBranch(initialTree, '');
|
||||
}
|
||||
|
||||
function printStats(stats: TraceResult[]): void {
|
||||
stats.sort((a, b) => a.start - b.start);
|
||||
|
||||
@ -10,3 +10,4 @@ DOMAIN-SUFFIX,openaiapi-site.azureedge.net
|
||||
DOMAIN-SUFFIX,perplexity.ai
|
||||
DOMAIN-SUFFIX,anthropic.com
|
||||
DOMAIN-SUFFIX,claude.ai
|
||||
DOMAIN-SUFFIX,generativelanguage.googleapis.com
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user