mirror of
https://github.com/SukkaW/Surge.git
synced 2025-12-12 01:00:34 +08:00
Chore: make ESLint Happy
This commit is contained in:
parent
40cda6997a
commit
e40979e50e
@ -64,46 +64,48 @@ export const buildCommon = task(require.main === module, __filename)(async (span
|
|||||||
|
|
||||||
const $skip = Symbol('skip');
|
const $skip = Symbol('skip');
|
||||||
|
|
||||||
const processFile = (span: Span, sourcePath: string) => span.traceChildAsync(`process file: ${sourcePath}`, async () => {
|
function processFile(span: Span, sourcePath: string) {
|
||||||
const lines: string[] = [];
|
return span.traceChildAsync(`process file: ${sourcePath}`, async () => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
const descriptions: string[] = [];
|
const descriptions: string[] = [];
|
||||||
let sgmodulePathname: string | null = null;
|
let sgmodulePathname: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const line of readFileByLine(sourcePath)) {
|
for await (const line of readFileByLine(sourcePath)) {
|
||||||
if (line.startsWith(MAGIC_COMMAND_SKIP)) {
|
if (line.startsWith(MAGIC_COMMAND_SKIP)) {
|
||||||
return $skip;
|
return $skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith(MAGIC_COMMAND_TITLE)) {
|
if (line.startsWith(MAGIC_COMMAND_TITLE)) {
|
||||||
title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
|
title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
|
if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
|
||||||
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
|
descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) {
|
if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) {
|
||||||
sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.length).trim();
|
sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.length).trim();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const l = processLine(line);
|
const l = processLine(line);
|
||||||
if (l) {
|
if (l) {
|
||||||
lines.push(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, sgmodulePathname] as const;
|
return [title, descriptions, lines, sgmodulePathname] as const;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) {
|
function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) {
|
||||||
return parentSpan
|
return parentSpan
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { appendArrayInPlace } from './lib/append-array-in-place';
|
|||||||
import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir';
|
import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir';
|
||||||
import { RulesetOutput } from './lib/create-file';
|
import { RulesetOutput } from './lib/create-file';
|
||||||
|
|
||||||
const getRule = (domain: string) => {
|
function getRule(domain: string) {
|
||||||
switch (domain[0]) {
|
switch (domain[0]) {
|
||||||
case '+':
|
case '+':
|
||||||
case '$':
|
case '$':
|
||||||
@ -20,7 +20,7 @@ const getRule = (domain: string) => {
|
|||||||
default:
|
default:
|
||||||
return `DOMAIN-SUFFIX,${domain}`;
|
return `DOMAIN-SUFFIX,${domain}`;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => {
|
export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => {
|
||||||
const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf'));
|
const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf'));
|
||||||
const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf'));
|
const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf'));
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { compareAndWriteFile } from './lib/create-file';
|
|||||||
const mockDir = path.join(ROOT_DIR, 'Mock');
|
const mockDir = path.join(ROOT_DIR, 'Mock');
|
||||||
const modulesDir = path.join(ROOT_DIR, 'Modules');
|
const modulesDir = path.join(ROOT_DIR, 'Modules');
|
||||||
|
|
||||||
const copyDirContents = async (srcDir: string, destDir: string) => {
|
async function copyDirContents(srcDir: string, destDir: string) {
|
||||||
const promises: Array<Promise<void>> = [];
|
const promises: Array<Promise<void>> = [];
|
||||||
|
|
||||||
for await (const entry of await fsp.opendir(srcDir)) {
|
for await (const entry of await fsp.opendir(srcDir)) {
|
||||||
@ -28,7 +28,7 @@ const copyDirContents = async (srcDir: string, destDir: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const buildPublic = task(require.main === module, __filename)(async (span) => {
|
export const buildPublic = task(require.main === module, __filename)(async (span) => {
|
||||||
await span.traceChildAsync('copy rest of the files', async () => {
|
await span.traceChildAsync('copy rest of the files', async () => {
|
||||||
@ -83,7 +83,7 @@ const prioritySorter = (a: TreeType, b: TreeType) => ((priorityOrder[a.name] ||
|
|||||||
|
|
||||||
const html = (string: TemplateStringsArray, ...values: any[]) => string.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
|
const html = (string: TemplateStringsArray, ...values: any[]) => string.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
|
||||||
|
|
||||||
const walk = (tree: TreeTypeArray) => {
|
function walk(tree: TreeTypeArray) {
|
||||||
let result = '';
|
let result = '';
|
||||||
tree.sort(prioritySorter);
|
tree.sort(prioritySorter);
|
||||||
for (let i = 0, len = tree.length; i < len; i++) {
|
for (let i = 0, len = tree.length; i < len; i++) {
|
||||||
@ -102,7 +102,7 @@ const walk = (tree: TreeTypeArray) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
function generateHtml(tree: TreeTypeArray) {
|
function generateHtml(tree: TreeTypeArray) {
|
||||||
return html`
|
return html`
|
||||||
|
|||||||
@ -141,7 +141,7 @@ const latestTopUserAgentsPromise = $fetch('https://cdn.jsdelivr.net/npm/top-user
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then((userAgents: string[]) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 ')));
|
.then((userAgents: string[]) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 ')));
|
||||||
|
|
||||||
const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>> => {
|
async function querySpeedtestApi(keyword: string): Promise<Array<string | null>> {
|
||||||
const topUserAgents = await latestTopUserAgentsPromise;
|
const topUserAgents = await latestTopUserAgentsPromise;
|
||||||
|
|
||||||
const url = `https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`;
|
const url = `https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`;
|
||||||
@ -181,7 +181,7 @@ const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>>
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => {
|
export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => {
|
||||||
const output = new DomainsetOutput(span, 'speedtest')
|
const output = new DomainsetOutput(span, 'speedtest')
|
||||||
|
|||||||
@ -6,33 +6,33 @@ import { ALL, NORTH_AMERICA, EU, HK, TW, JP, KR } from '../Source/stream';
|
|||||||
import { SHARED_DESCRIPTION } from './lib/constants';
|
import { SHARED_DESCRIPTION } from './lib/constants';
|
||||||
import { RulesetOutput } from './lib/create-file';
|
import { RulesetOutput } from './lib/create-file';
|
||||||
|
|
||||||
export const createRulesetForStreamService = (
|
export function createRulesetForStreamService(span: Span,
|
||||||
span: Span,
|
|
||||||
fileId: string, title: string,
|
fileId: string, title: string,
|
||||||
streamServices: Array<import('../Source/stream').StreamService>
|
streamServices: Array<import('../Source/stream').StreamService>) {
|
||||||
) => span.traceChildAsync(fileId, async (childSpan) => Promise.all([
|
return span.traceChildAsync(fileId, async (childSpan) => Promise.all([
|
||||||
// Domains
|
// Domains
|
||||||
new RulesetOutput(childSpan, fileId, 'non_ip')
|
new RulesetOutput(childSpan, fileId, 'non_ip')
|
||||||
.withTitle(`Sukka's Ruleset - Stream Services: ${title}`)
|
.withTitle(`Sukka's Ruleset - Stream Services: ${title}`)
|
||||||
.withDescription([
|
.withDescription([
|
||||||
...SHARED_DESCRIPTION,
|
...SHARED_DESCRIPTION,
|
||||||
'',
|
'',
|
||||||
...streamServices.map((i) => `- ${i.name}`)
|
...streamServices.map((i) => `- ${i.name}`)
|
||||||
])
|
])
|
||||||
.addFromRuleset(streamServices.flatMap((i) => i.rules))
|
.addFromRuleset(streamServices.flatMap((i) => i.rules))
|
||||||
.write(),
|
.write(),
|
||||||
// IP
|
// IP
|
||||||
new RulesetOutput(childSpan, fileId, 'ip')
|
new RulesetOutput(childSpan, fileId, 'ip')
|
||||||
.withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`)
|
.withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`)
|
||||||
.withDescription([
|
.withDescription([
|
||||||
...SHARED_DESCRIPTION,
|
...SHARED_DESCRIPTION,
|
||||||
'',
|
'',
|
||||||
...streamServices.map((i) => `- ${i.name}`)
|
...streamServices.map((i) => `- ${i.name}`)
|
||||||
])
|
])
|
||||||
.bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? []))
|
.bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? []))
|
||||||
.bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? []))
|
.bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? []))
|
||||||
.write()
|
.write()
|
||||||
]));
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
export const buildStreamService = task(require.main === module, __filename)(async (span) => Promise.all([
|
export const buildStreamService = task(require.main === module, __filename)(async (span) => Promise.all([
|
||||||
createRulesetForStreamService(span, 'stream', 'All', ALL),
|
createRulesetForStreamService(span, 'stream', 'All', ALL),
|
||||||
|
|||||||
@ -6,14 +6,14 @@ type Node = Map<string, Node> & {
|
|||||||
[FAIL]: Node | undefined
|
[FAIL]: Node | undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNode = (): Node => {
|
function createNode(): Node {
|
||||||
const node = new Map<string, Node | undefined>() as Node;
|
const node = new Map<string, Node | undefined>() as Node;
|
||||||
node[WORDEND] = false;
|
node[WORDEND] = false;
|
||||||
node[FAIL] = undefined;
|
node[FAIL] = undefined;
|
||||||
return node;
|
return node;
|
||||||
};
|
}
|
||||||
|
|
||||||
const createKeywordFilter = (keys: string[] | Set<string>) => {
|
function createKeywordFilter(keys: string[] | Set<string>) {
|
||||||
const root = createNode();
|
const root = createNode();
|
||||||
|
|
||||||
// Create a trie with extra fields and information
|
// Create a trie with extra fields and information
|
||||||
@ -82,6 +82,6 @@ const createKeywordFilter = (keys: string[] | Set<string>) => {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default createKeywordFilter;
|
export default createKeywordFilter;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { Writable } from 'node:stream';
|
import type { Writable } from 'node:stream';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
|
|
||||||
export const asyncWriteToStream = <T>(stream: Writable, chunk: T) => {
|
export function asyncWriteToStream<T>(stream: Writable, chunk: T) {
|
||||||
const res = stream.write(chunk);
|
const res = stream.write(chunk);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return once(stream, 'drain'); // returns a promise only if needed
|
return once(stream, 'drain'); // returns a promise only if needed
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
export const pack = (a: number, b: number): number => (a << 16) | b;
|
export const pack = (a: number, b: number): number => (a << 16) | b;
|
||||||
|
|
||||||
/** Unpacks two 16-bit integers from one 32-bit integer */
|
/** Unpacks two 16-bit integers from one 32-bit integer */
|
||||||
export const unpack = (value: number, arr: [a: number, b: number] = Array.from(new Array(2).keys()) as any): [a: number, b: number] => {
|
export function unpack(value: number, arr: [a: number, b: number] = Array.from(new Array(2).keys()) as any): [a: number, b: number] {
|
||||||
arr[0] = (value >> 16) & 0xFFFF;
|
arr[0] = (value >> 16) & 0xFFFF;
|
||||||
arr[1] = value & 0xFFFF;
|
arr[1] = value & 0xFFFF;
|
||||||
return arr;
|
return arr;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const unpackFirst = (value: number): number => (value >> 16) & 0xFFFF;
|
export const unpackFirst = (value: number): number => (value >> 16) & 0xFFFF;
|
||||||
export const unpackSecond = (value: number): number => value & 0xFFFF;
|
export const unpackSecond = (value: number): number => value & 0xFFFF;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
||||||
export const createCache = (namespace?: string, printStats = false) => {
|
export function createCache(namespace?: string, printStats = false) {
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
|
|
||||||
let hit = 0;
|
let hit = 0;
|
||||||
@ -30,4 +30,4 @@ export const createCache = (namespace?: string, printStats = false) => {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
@ -410,7 +410,7 @@ export const serializeArray = (arr: string[]) => fastStringArrayJoin(arr, separa
|
|||||||
export const deserializeArray = (str: string) => str.split(separator);
|
export const deserializeArray = (str: string) => str.split(separator);
|
||||||
|
|
||||||
export const getFileContentHash = (filename: string) => stringHash(fs.readFileSync(filename, 'utf-8'));
|
export const getFileContentHash = (filename: string) => stringHash(fs.readFileSync(filename, 'utf-8'));
|
||||||
export const createCacheKey = (filename: string) => {
|
export function createCacheKey(filename: string) {
|
||||||
const fileHash = getFileContentHash(filename);
|
const fileHash = getFileContentHash(filename);
|
||||||
return (key: string) => key + '$' + fileHash + '$';
|
return (key: string) => key + '$' + fileHash + '$';
|
||||||
};
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const mihomoBinaryUrl: Partial<Record<NodeJS.Platform, Partial<Record<NodeJS.Arc
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ensureMihomoBinary = async () => {
|
async function ensureMihomoBinary() {
|
||||||
await mkdirp(mihomoBinaryDir);
|
await mkdirp(mihomoBinaryDir);
|
||||||
if (!fs.existsSync(mihomoBinaryPath)) {
|
if (!fs.existsSync(mihomoBinaryPath)) {
|
||||||
const writeStream = fs.createWriteStream(mihomoBinaryPath);
|
const writeStream = fs.createWriteStream(mihomoBinaryPath);
|
||||||
@ -47,9 +47,9 @@ const ensureMihomoBinary = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
await fsp.chmod(mihomoBinaryPath, 0o755);
|
await fsp.chmod(mihomoBinaryPath, 0o755);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const convertClashMetaMrs = async (type: 'domain', format: 'text', input: string, output: string) => {
|
export async function convertClashMetaMrs(type: 'domain', format: 'text', input: string, output: string) {
|
||||||
await ensureMihomoBinary();
|
await ensureMihomoBinary();
|
||||||
|
|
||||||
const { stderr } = await ezspawn(mihomoBinaryPath, ['convert-ruleset', type, format, input, output]);
|
const { stderr } = await ezspawn(mihomoBinaryPath, ['convert-ruleset', type, format, input, output]);
|
||||||
@ -57,4 +57,4 @@ export const convertClashMetaMrs = async (type: 'domain', format: 'text', input:
|
|||||||
if (stderr) {
|
if (stderr) {
|
||||||
throw new Error(stderr);
|
throw new Error(stderr);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@ -8,9 +8,9 @@ const createSource = async function *(input: string[]) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const test = async (a: string[], b: string[], expected: boolean) => {
|
async function test(a: string[], b: string[], expected: boolean) {
|
||||||
expect((await fileEqual(a, createSource(b)))).to.eq(expected);
|
expect((await fileEqual(a, createSource(b)))).to.eq(expected);
|
||||||
};
|
}
|
||||||
|
|
||||||
describe('fileEqual', () => {
|
describe('fileEqual', () => {
|
||||||
it('same', () => test(
|
it('same', () => test(
|
||||||
|
|||||||
@ -26,19 +26,21 @@ export class CustomNoETagFallbackError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sleepWithAbort = (ms: number, signal: AbortSignal) => new Promise<void>((resolve, reject) => {
|
export function sleepWithAbort(ms: number, signal: AbortSignal) {
|
||||||
if (signal.aborted) {
|
return new Promise<void>((resolve, reject) => {
|
||||||
reject(signal.reason as Error);
|
if (signal.aborted) {
|
||||||
return;
|
reject(signal.reason as Error);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
signal.addEventListener('abort', stop, { once: true });
|
signal.addEventListener('abort', stop, { once: true });
|
||||||
|
|
||||||
// eslint-disable-next-line sukka/prefer-timer-id -- node:timers/promises
|
// eslint-disable-next-line sukka/prefer-timer-id -- node:timers/promises
|
||||||
setTimeout(ms, undefined, { ref: false }).then(resolve).catch(reject).finally(() => signal.removeEventListener('abort', stop));
|
setTimeout(ms, undefined, { ref: false }).then(resolve).catch(reject).finally(() => signal.removeEventListener('abort', stop));
|
||||||
|
|
||||||
function stop(this: AbortSignal) { reject(this.reason as Error); }
|
function stop(this: AbortSignal) { reject(this.reason as Error); }
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchAssets(url: string, fallbackUrls: string[] | readonly string[]) {
|
export async function fetchAssets(url: string, fallbackUrls: string[] | readonly string[]) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|||||||
@ -95,26 +95,6 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const mayBailError = (err: unknown) => {
|
|
||||||
if (typeof err === 'object' && err !== null && 'name' in err) {
|
|
||||||
if ((
|
|
||||||
err.name === 'AbortError'
|
|
||||||
|| ('digest' in err && err.digest === 'AbortError')
|
|
||||||
) && !retryOpts.retryOnAborted) {
|
|
||||||
console.log(picocolors.gray('[fetch abort]'), url);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (err.name === 'Custom304NotModifiedError') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (err.name === 'CustomNoETagFallbackError') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!(isClientError(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mayBailError(err)) {
|
if (mayBailError(err)) {
|
||||||
return bail(err) as never;
|
return bail(err) as never;
|
||||||
};
|
};
|
||||||
@ -140,6 +120,26 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry {
|
|||||||
throw newErr;
|
throw newErr;
|
||||||
}
|
}
|
||||||
}, retryOpts);
|
}, retryOpts);
|
||||||
|
|
||||||
|
function mayBailError(err: unknown) {
|
||||||
|
if (typeof err === 'object' && err !== null && 'name' in err) {
|
||||||
|
if ((
|
||||||
|
err.name === 'AbortError'
|
||||||
|
|| ('digest' in err && err.digest === 'AbortError')
|
||||||
|
) && !retryOpts.retryOnAborted) {
|
||||||
|
console.log(picocolors.gray('[fetch abort]'), url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (err.name === 'Custom304NotModifiedError') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (err.name === 'CustomNoETagFallbackError') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!(isClientError(err));
|
||||||
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ResponseError) {
|
if (err instanceof ResponseError) {
|
||||||
return err.res;
|
return err.res;
|
||||||
|
|||||||
@ -9,19 +9,19 @@ import { processLine } from './process-line';
|
|||||||
import { $fetch } from './make-fetch-happen';
|
import { $fetch } from './make-fetch-happen';
|
||||||
import type { NodeFetchResponse } from './make-fetch-happen';
|
import type { NodeFetchResponse } from './make-fetch-happen';
|
||||||
|
|
||||||
const getReadableStream = (file: string | FileHandle): ReadableStream => {
|
function getReadableStream(file: string | FileHandle): ReadableStream {
|
||||||
if (typeof file === 'string') {
|
if (typeof file === 'string') {
|
||||||
// return fs.openAsBlob(file).then(blob => blob.stream())
|
// return fs.openAsBlob(file).then(blob => blob.stream())
|
||||||
return Readable.toWeb(fs.createReadStream(file/* , { encoding: 'utf-8' } */));
|
return Readable.toWeb(fs.createReadStream(file/* , { encoding: 'utf-8' } */));
|
||||||
}
|
}
|
||||||
return file.readableWebStream();
|
return file.readableWebStream();
|
||||||
};
|
}
|
||||||
// TODO: use FileHandle.readLine()
|
// TODO: use FileHandle.readLine()
|
||||||
export const readFileByLine: ((file: string | FileHandle) => AsyncIterable<string>) = (file: string | FileHandle) => getReadableStream(file)
|
export const readFileByLine: ((file: string | FileHandle) => AsyncIterable<string>) = (file: string | FileHandle) => getReadableStream(file)
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new TextLineStream());
|
.pipeThrough(new TextLineStream());
|
||||||
|
|
||||||
const ensureResponseBody = <T extends Response | NodeFetchResponse>(resp: T): NonNullable<T['body']> => {
|
function ensureResponseBody<T extends Response | NodeFetchResponse>(resp: T): NonNullable<T['body']> {
|
||||||
if (!resp.body) {
|
if (!resp.body) {
|
||||||
throw new Error('Failed to fetch remote text');
|
throw new Error('Failed to fetch remote text');
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ const ensureResponseBody = <T extends Response | NodeFetchResponse>(resp: T): No
|
|||||||
throw new Error('Body has already been consumed.');
|
throw new Error('Body has already been consumed.');
|
||||||
}
|
}
|
||||||
return resp.body;
|
return resp.body;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const createReadlineInterfaceFromResponse: ((resp: Response | NodeFetchResponse) => AsyncIterable<string>) = (resp) => {
|
export const createReadlineInterfaceFromResponse: ((resp: Response | NodeFetchResponse) => AsyncIterable<string>) = (resp) => {
|
||||||
const stream = ensureResponseBody(resp);
|
const stream = ensureResponseBody(resp);
|
||||||
|
|||||||
@ -101,25 +101,27 @@ const lowKeywords = createKeywordFilter([
|
|||||||
|
|
||||||
const cacheKey = createCacheKey(__filename);
|
const cacheKey = createCacheKey(__filename);
|
||||||
|
|
||||||
export const getPhishingDomains = (parentSpan: Span) => parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => {
|
export function getPhishingDomains(parentSpan: Span) {
|
||||||
const domainArr = await span.traceChildAsync('download/parse/merge phishing domains', async (curSpan) => {
|
return parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => {
|
||||||
const domainArr: string[] = [];
|
const domainArr = await span.traceChildAsync('download/parse/merge phishing domains', async (curSpan) => {
|
||||||
|
const domainArr: string[] = [];
|
||||||
|
|
||||||
(await Promise.all(PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainLists(curSpan, ...entry, cacheKey))))
|
(await Promise.all(PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainLists(curSpan, ...entry, cacheKey))))
|
||||||
.forEach(appendArrayInPlaceCurried(domainArr));
|
.forEach(appendArrayInPlaceCurried(domainArr));
|
||||||
(await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey))))
|
(await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey))))
|
||||||
.forEach(appendArrayInPlaceCurried(domainArr));
|
.forEach(appendArrayInPlaceCurried(domainArr));
|
||||||
|
|
||||||
return domainArr;
|
return domainArr;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cacheHash = span.traceChildSync('get hash', () => stringHash(fastStringArrayJoin(domainArr, '|')));
|
||||||
|
|
||||||
|
return span.traceChildAsync(
|
||||||
|
'process phishing domain set',
|
||||||
|
() => processPhihsingDomains(domainArr, cacheHash)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
const cacheHash = span.traceChildSync('get hash', () => stringHash(fastStringArrayJoin(domainArr, '|')));
|
|
||||||
|
|
||||||
return span.traceChildAsync(
|
|
||||||
'process phishing domain set',
|
|
||||||
() => processPhihsingDomains(domainArr, cacheHash)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function processPhihsingDomains(domainArr: string[], cacheHash = '') {
|
async function processPhihsingDomains(domainArr: string[], cacheHash = '') {
|
||||||
return fsFetchCache.apply(
|
return fsFetchCache.apply(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const notError = Symbol('notError');
|
const notError = Symbol('notError');
|
||||||
|
|
||||||
export const createMemoizedPromise = <T>(fn: () => Promise<T>, preload = true): () => Promise<T> => {
|
export function createMemoizedPromise<T>(fn: () => Promise<T>, preload = true): () => Promise<T> {
|
||||||
let error: Error | typeof notError = notError;
|
let error: Error | typeof notError = notError;
|
||||||
|
|
||||||
let promise: Promise<T> | null = preload
|
let promise: Promise<T> | null = preload
|
||||||
@ -19,4 +19,4 @@ export const createMemoizedPromise = <T>(fn: () => Promise<T>, preload = true):
|
|||||||
promise ??= fn();
|
promise ??= fn();
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { OUTPUT_CLASH_DIR, OUTPUT_SINGBOX_DIR, OUTPUT_SURGE_DIR } from '../const
|
|||||||
|
|
||||||
export const isTruthy = <T>(i: T | 0 | '' | false | null | undefined): i is T => !!i;
|
export const isTruthy = <T>(i: T | 0 | '' | false | null | undefined): i is T => !!i;
|
||||||
|
|
||||||
export const fastStringArrayJoin = (arr: string[], sep: string) => {
|
export function fastStringArrayJoin(arr: string[], sep: string) {
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let i = 0, len = arr.length; i < len; i++) {
|
for (let i = 0, len = arr.length; i < len; i++) {
|
||||||
if (i !== 0) {
|
if (i !== 0) {
|
||||||
@ -14,7 +14,7 @@ export const fastStringArrayJoin = (arr: string[], sep: string) => {
|
|||||||
result += arr[i];
|
result += arr[i];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface Write {
|
interface Write {
|
||||||
(
|
(
|
||||||
@ -23,12 +23,12 @@ interface Write {
|
|||||||
): Promise<unknown>
|
): Promise<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mkdirp = (dir: string) => {
|
export function mkdirp(dir: string) {
|
||||||
if (fs.existsSync(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return fsp.mkdir(dir, { recursive: true });
|
return fsp.mkdir(dir, { recursive: true });
|
||||||
};
|
}
|
||||||
|
|
||||||
export const writeFile: Write = async (destination: string, input, dir = dirname(destination)) => {
|
export const writeFile: Write = async (destination: string, input, dir = dirname(destination)) => {
|
||||||
const p = mkdirp(dir);
|
const p = mkdirp(dir);
|
||||||
@ -40,7 +40,7 @@ export const writeFile: Write = async (destination: string, input, dir = dirname
|
|||||||
|
|
||||||
export const removeFiles = async (files: string[]) => Promise.all(files.map((file) => fsp.rm(file, { force: true })));
|
export const removeFiles = async (files: string[]) => Promise.all(files.map((file) => fsp.rm(file, { force: true })));
|
||||||
|
|
||||||
export const domainWildCardToRegex = (domain: string) => {
|
export function domainWildCardToRegex(domain: string) {
|
||||||
let result = '^';
|
let result = '^';
|
||||||
for (let i = 0, len = domain.length; i < len; i++) {
|
for (let i = 0, len = domain.length; i < len; i++) {
|
||||||
switch (domain[i]) {
|
switch (domain[i]) {
|
||||||
@ -59,11 +59,11 @@ export const domainWildCardToRegex = (domain: string) => {
|
|||||||
}
|
}
|
||||||
result += '$';
|
result += '$';
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const identity = <T>(x: T): T => x;
|
export const identity = <T>(x: T): T => x;
|
||||||
|
|
||||||
export const appendArrayFromSet = <T>(dest: T[], source: Set<T> | Array<Set<T>>, transformer: (item: T) => T = identity) => {
|
export function appendArrayFromSet<T>(dest: T[], source: Set<T> | Array<Set<T>>, transformer: (item: T) => T = identity) {
|
||||||
const casted = Array.isArray(source) ? source : [source];
|
const casted = Array.isArray(source) ? source : [source];
|
||||||
for (let i = 0, len = casted.length; i < len; i++) {
|
for (let i = 0, len = casted.length; i < len; i++) {
|
||||||
const iterator = casted[i].values();
|
const iterator = casted[i].values();
|
||||||
@ -75,13 +75,15 @@ export const appendArrayFromSet = <T>(dest: T[], source: Set<T> | Array<Set<T>>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const output = (id: string, type: 'non_ip' | 'ip' | 'domainset') => [
|
export function output(id: string, type: 'non_ip' | 'ip' | 'domainset') {
|
||||||
path.join(OUTPUT_SURGE_DIR, type, id + '.conf'),
|
return [
|
||||||
path.join(OUTPUT_CLASH_DIR, type, id + '.txt'),
|
path.join(OUTPUT_SURGE_DIR, type, id + '.conf'),
|
||||||
path.join(OUTPUT_SINGBOX_DIR, type, id + '.json')
|
path.join(OUTPUT_CLASH_DIR, type, id + '.txt'),
|
||||||
] as const;
|
path.join(OUTPUT_SINGBOX_DIR, type, id + '.json')
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
export function withBannerArray(title: string, description: string[] | readonly string[], date: Date, content: string[]) {
|
export function withBannerArray(title: string, description: string[] | readonly string[], date: Date, content: string[]) {
|
||||||
return [
|
return [
|
||||||
@ -96,7 +98,7 @@ export function withBannerArray(title: string, description: string[] | readonly
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mergeHeaders = (headersA: RequestInit['headers'] | undefined, headersB: RequestInit['headers']) => {
|
export function mergeHeaders(headersA: RequestInit['headers'] | undefined, headersB: RequestInit['headers']) {
|
||||||
if (headersA == null) {
|
if (headersA == null) {
|
||||||
return headersB;
|
return headersB;
|
||||||
}
|
}
|
||||||
@ -116,9 +118,9 @@ export const mergeHeaders = (headersA: RequestInit['headers'] | undefined, heade
|
|||||||
|
|
||||||
for (const key in headersB) {
|
for (const key in headersB) {
|
||||||
if (Object.hasOwn(headersB, key)) {
|
if (Object.hasOwn(headersB, key)) {
|
||||||
result.set(key, (headersB as Record<string, string>)[key]);
|
result.set(key, (headersB)[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { normalizeTldtsOpt } from '../constants/loose-tldts-opt';
|
|||||||
|
|
||||||
type TldTsParsed = ReturnType<typeof tldts.parse>;
|
type TldTsParsed = ReturnType<typeof tldts.parse>;
|
||||||
|
|
||||||
export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = null) => {
|
export function normalizeDomain(domain: string, parsed: TldTsParsed | null = null) {
|
||||||
if (domain.length === 0) return null;
|
if (domain.length === 0) return null;
|
||||||
|
|
||||||
parsed ??= tldts.parse(domain, normalizeTldtsOpt);
|
parsed ??= tldts.parse(domain, normalizeTldtsOpt);
|
||||||
@ -29,4 +29,4 @@ export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = nul
|
|||||||
}
|
}
|
||||||
|
|
||||||
return h.length > 0 ? h : null;
|
return h.length > 0 ? h : null;
|
||||||
};
|
}
|
||||||
|
|||||||
@ -3,19 +3,19 @@ import { parse as tldtsParse } from 'tldts';
|
|||||||
import { $fetch } from './make-fetch-happen';
|
import { $fetch } from './make-fetch-happen';
|
||||||
import type { NodeFetchResponse } from './make-fetch-happen';
|
import type { NodeFetchResponse } from './make-fetch-happen';
|
||||||
|
|
||||||
const isDomainLoose = (domain: string): boolean => {
|
function isDomainLoose(domain: string): boolean {
|
||||||
const { isIcann, isPrivate, isIp } = tldtsParse(domain);
|
const { isIcann, isPrivate, isIp } = tldtsParse(domain);
|
||||||
return !!(!isIp && (isIcann || isPrivate));
|
return !!(!isIp && (isIcann || isPrivate));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const extractDomainsFromFelixDnsmasq = (line: string): string | null => {
|
export function extractDomainsFromFelixDnsmasq(line: string): string | null {
|
||||||
if (line.startsWith('server=/') && line.endsWith('/114.114.114.114')) {
|
if (line.startsWith('server=/') && line.endsWith('/114.114.114.114')) {
|
||||||
return line.slice(8, -16);
|
return line.slice(8, -16);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchResponse): Promise<string[]> => {
|
export async function parseFelixDnsmasqFromResp(resp: Response | NodeFetchResponse): Promise<string[]> {
|
||||||
const results: string[] = [];
|
const results: string[] = [];
|
||||||
|
|
||||||
for await (const line of createReadlineInterfaceFromResponse(resp)) {
|
for await (const line of createReadlineInterfaceFromResponse(resp)) {
|
||||||
@ -26,9 +26,9 @@ export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const parseFelixDnsmasq = async (url: string): Promise<string[]> => {
|
export async function parseFelixDnsmasq(url: string): Promise<string[]> {
|
||||||
const resp = await $fetch(url);
|
const resp = await $fetch(url);
|
||||||
return parseFelixDnsmasqFromResp(resp);
|
return parseFelixDnsmasqFromResp(resp);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const DEBUG_DOMAIN_TO_FIND: string | null = null; // example.com | null
|
|||||||
let foundDebugDomain = false;
|
let foundDebugDomain = false;
|
||||||
const temporaryBypass = typeof DEBUG_DOMAIN_TO_FIND === 'string';
|
const temporaryBypass = typeof DEBUG_DOMAIN_TO_FIND === 'string';
|
||||||
|
|
||||||
const domainListLineCb = (l: string, set: string[], includeAllSubDomain: boolean, meta: string) => {
|
function domainListLineCb(l: string, set: string[], includeAllSubDomain: boolean, meta: string) {
|
||||||
let line = processLine(l);
|
let line = processLine(l);
|
||||||
if (!line) return;
|
if (!line) return;
|
||||||
line = line.toLowerCase();
|
line = line.toLowerCase();
|
||||||
@ -39,7 +39,7 @@ const domainListLineCb = (l: string, set: string[], includeAllSubDomain: boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
set.push(includeAllSubDomain ? `.${line}` : line);
|
set.push(includeAllSubDomain ? `.${line}` : line);
|
||||||
};
|
}
|
||||||
|
|
||||||
export function processDomainLists(
|
export function processDomainLists(
|
||||||
span: Span,
|
span: Span,
|
||||||
@ -71,7 +71,7 @@ export function processDomainLists(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostsLineCb = (l: string, set: string[], includeAllSubDomain: boolean, meta: string) => {
|
function hostsLineCb(l: string, set: string[], includeAllSubDomain: boolean, meta: string) {
|
||||||
const line = processLine(l);
|
const line = processLine(l);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return;
|
return;
|
||||||
@ -91,7 +91,7 @@ const hostsLineCb = (l: string, set: string[], includeAllSubDomain: boolean, met
|
|||||||
}
|
}
|
||||||
|
|
||||||
set.push(includeAllSubDomain ? `.${domain}` : domain);
|
set.push(includeAllSubDomain ? `.${domain}` : domain);
|
||||||
};
|
}
|
||||||
|
|
||||||
export function processHosts(
|
export function processHosts(
|
||||||
span: Span,
|
span: Span,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export const processLine = (line: string): string | null => {
|
export function processLine(line: string): string | null {
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -22,9 +22,9 @@ export const processLine = (line: string): string | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return trimmed;
|
return trimmed;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const processLineFromReadline = async (rl: AsyncIterable<string>): Promise<string[]> => {
|
export async function processLineFromReadline(rl: AsyncIterable<string>): Promise<string[]> {
|
||||||
const res: string[] = [];
|
const res: string[] = [];
|
||||||
for await (const line of rl) {
|
for await (const line of rl) {
|
||||||
const l: string | null = processLine(line);
|
const l: string | null = processLine(line);
|
||||||
@ -33,4 +33,4 @@ export const processLineFromReadline = async (rl: AsyncIterable<string>): Promis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
}
|
||||||
|
|||||||
@ -125,6 +125,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async addFromDomainsetPromise(source: AsyncIterable<string> | Iterable<string> | string[]) {
|
private async addFromDomainsetPromise(source: AsyncIterable<string> | Iterable<string> | string[]) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/await-thenable -- https://github.com/typescript-eslint/typescript-eslint/issues/10080
|
||||||
for await (const line of source) {
|
for await (const line of source) {
|
||||||
if (line[0] === '.') {
|
if (line[0] === '.') {
|
||||||
this.addDomainSuffix(line);
|
this.addDomainSuffix(line);
|
||||||
@ -140,6 +141,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async addFromRulesetPromise(source: AsyncIterable<string> | Iterable<string>) {
|
private async addFromRulesetPromise(source: AsyncIterable<string> | Iterable<string>) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/await-thenable -- https://github.com/typescript-eslint/typescript-eslint/issues/10080
|
||||||
for await (const line of source) {
|
for await (const line of source) {
|
||||||
const splitted = line.split(',');
|
const splitted = line.split(',');
|
||||||
const type = splitted[0];
|
const type = splitted[0];
|
||||||
@ -314,7 +316,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
abstract mitmSgmodule?(): string[] | null;
|
abstract mitmSgmodule?(): string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fileEqual = async (linesA: string[], source: AsyncIterable<string>): Promise<boolean> => {
|
export async function fileEqual(linesA: string[], source: AsyncIterable<string>): Promise<boolean> {
|
||||||
if (linesA.length === 0) {
|
if (linesA.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -350,7 +352,7 @@ export const fileEqual = async (linesA: string[], source: AsyncIterable<string>)
|
|||||||
|
|
||||||
// The file becomes larger
|
// The file becomes larger
|
||||||
return !(index < linesA.length - 1);
|
return !(index < linesA.length - 1);
|
||||||
};
|
}
|
||||||
|
|
||||||
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
|
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
|
||||||
let isEqual = true;
|
let isEqual = true;
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { isProbablyIpv4, isProbablyIpv6 } from './is-fast-ip';
|
|||||||
|
|
||||||
const unsupported = Symbol('unsupported');
|
const unsupported = Symbol('unsupported');
|
||||||
|
|
||||||
const toNumberTuple = <T extends string>(key: T, value: string): [T, number] | null => {
|
function toNumberTuple<T extends string>(key: T, value: string): [T, number] | null {
|
||||||
const tmp = Number(value);
|
const tmp = Number(value);
|
||||||
return Number.isNaN(tmp) ? null : [key, tmp];
|
return Number.isNaN(tmp) ? null : [key, tmp];
|
||||||
};
|
}
|
||||||
|
|
||||||
// https://sing-box.sagernet.org/configuration/rule-set/source-format/
|
// https://sing-box.sagernet.org/configuration/rule-set/source-format/
|
||||||
export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = {
|
export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = {
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
import * as tldts from 'tldts-experimental';
|
import * as tldts from 'tldts-experimental';
|
||||||
import { looseTldtsOpt } from '../constants/loose-tldts-opt';
|
import { looseTldtsOpt } from '../constants/loose-tldts-opt';
|
||||||
|
|
||||||
export const compare = (a: string, b: string) => {
|
export function compare(a: string, b: string) {
|
||||||
if (a === b) return 0;
|
if (a === b) return 0;
|
||||||
return (a.length - b.length) || a.localeCompare(b);
|
return (a.length - b.length) || a.localeCompare(b);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const buildParseDomainMap = (inputs: string[]) => {
|
export function buildParseDomainMap(inputs: string[]) {
|
||||||
const domainMap = new Map<string, string>();
|
const domainMap = new Map<string, string>();
|
||||||
const subdomainMap = new Map<string, string>();
|
const subdomainMap = new Map<string, string>();
|
||||||
|
|
||||||
@ -24,13 +24,11 @@ export const buildParseDomainMap = (inputs: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { domainMap, subdomainMap };
|
return { domainMap, subdomainMap };
|
||||||
};
|
}
|
||||||
|
|
||||||
export const sortDomains = (
|
export function sortDomains(inputs: string[],
|
||||||
inputs: string[],
|
|
||||||
domainMap?: Map<string, string> | null,
|
domainMap?: Map<string, string> | null,
|
||||||
subdomainMap?: Map<string, string> | null
|
subdomainMap?: Map<string, string> | null) {
|
||||||
) => {
|
|
||||||
if (!domainMap || !subdomainMap) {
|
if (!domainMap || !subdomainMap) {
|
||||||
const { domainMap: dm, subdomainMap: sm } = buildParseDomainMap(inputs);
|
const { domainMap: dm, subdomainMap: sm } = buildParseDomainMap(inputs);
|
||||||
domainMap = dm;
|
domainMap = dm;
|
||||||
@ -58,4 +56,4 @@ export const sortDomains = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
return inputs.sort(sorter);
|
return inputs.sort(sorter);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
* Simplified, optimized and add modified for 52 bit, which provides a larger hash space
|
* Simplified, optimized and add modified for 52 bit, which provides a larger hash space
|
||||||
* and still making use of Javascript's 53-bit integer space.
|
* and still making use of Javascript's 53-bit integer space.
|
||||||
*/
|
*/
|
||||||
export const fnv1a52 = (str: string) => {
|
export function fnv1a52(str: string) {
|
||||||
const len = str.length;
|
const len = str.length;
|
||||||
let i = 0,
|
let i = 0,
|
||||||
t0 = 0,
|
t0 = 0,
|
||||||
@ -41,6 +41,6 @@ export const fnv1a52 = (str: string) => {
|
|||||||
+ v1 * 65536
|
+ v1 * 65536
|
||||||
+ (v0 ^ (v3 >> 4))
|
+ (v0 ^ (v3 >> 4))
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const stringHash = (payload: string) => fnv1a52(payload).toString(36) + payload.length.toString(36);
|
export const stringHash = (payload: string) => fnv1a52(payload).toString(36) + payload.length.toString(36);
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export type TreeTypeArray = TreeType[];
|
|||||||
|
|
||||||
type VoidOrVoidArray = void | VoidOrVoidArray[];
|
type VoidOrVoidArray = void | VoidOrVoidArray[];
|
||||||
|
|
||||||
export const treeDir = async (rootPath: string): Promise<TreeTypeArray> => {
|
export async function treeDir(rootPath: string): Promise<TreeTypeArray> {
|
||||||
const tree: TreeTypeArray = [];
|
const tree: TreeTypeArray = [];
|
||||||
|
|
||||||
const walk = async (dir: string, node: TreeTypeArray, dirRelativeToRoot = ''): Promise<VoidOrVoidArray> => {
|
const walk = async (dir: string, node: TreeTypeArray, dirRelativeToRoot = ''): Promise<VoidOrVoidArray> => {
|
||||||
@ -60,4 +60,4 @@ export const treeDir = async (rootPath: string): Promise<TreeTypeArray> => {
|
|||||||
await walk(rootPath, tree);
|
await walk(rootPath, tree);
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
};
|
}
|
||||||
|
|||||||
@ -13,10 +13,8 @@ type TrieNode<Meta = any> = [
|
|||||||
Meta /** meta */
|
Meta /** meta */
|
||||||
];
|
];
|
||||||
|
|
||||||
const deepTrieNodeToJSON = (
|
function deepTrieNodeToJSON(node: TrieNode,
|
||||||
node: TrieNode,
|
unpackMeta: ((meta?: any) => string) | undefined) {
|
||||||
unpackMeta: ((meta?: any) => string) | undefined
|
|
||||||
) => {
|
|
||||||
const obj: Record<string, any> = {};
|
const obj: Record<string, any> = {};
|
||||||
if (node[0]) {
|
if (node[0]) {
|
||||||
obj['[start]'] = node[0];
|
obj['[start]'] = node[0];
|
||||||
@ -32,11 +30,11 @@ const deepTrieNodeToJSON = (
|
|||||||
obj[key] = deepTrieNodeToJSON(value, unpackMeta);
|
obj[key] = deepTrieNodeToJSON(value, unpackMeta);
|
||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
};
|
}
|
||||||
|
|
||||||
const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [false, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;
|
const createNode = <Meta = any>(parent: TrieNode | null = null): TrieNode => [false, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;
|
||||||
|
|
||||||
export const hostnameToTokens = (hostname: string): string[] => {
|
export function hostnameToTokens(hostname: string): string[] {
|
||||||
const tokens = hostname.split('.');
|
const tokens = hostname.split('.');
|
||||||
const results: string[] = [];
|
const results: string[] = [];
|
||||||
let token = '';
|
let token = '';
|
||||||
@ -51,9 +49,9 @@ export const hostnameToTokens = (hostname: string): string[] => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
};
|
}
|
||||||
|
|
||||||
const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolean | null): boolean | null => {
|
function walkHostnameTokens(hostname: string, onToken: (token: string) => boolean | null): boolean | null {
|
||||||
const tokens = hostname.split('.');
|
const tokens = hostname.split('.');
|
||||||
let token = '';
|
let token = '';
|
||||||
|
|
||||||
@ -78,7 +76,7 @@ const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolea
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface FindSingleChildLeafResult<Meta> {
|
interface FindSingleChildLeafResult<Meta> {
|
||||||
node: TrieNode<Meta>,
|
node: TrieNode<Meta>,
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export interface Span {
|
|||||||
readonly traceResult: TraceResult
|
readonly traceResult: TraceResult
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSpan = (name: string, parentTraceResult?: TraceResult): Span => {
|
export function createSpan(name: string, parentTraceResult?: TraceResult): Span {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
|
|
||||||
let curTraceResult: TraceResult;
|
let curTraceResult: TraceResult;
|
||||||
@ -93,27 +93,29 @@ export const createSpan = (name: string, parentTraceResult?: TraceResult): Span
|
|||||||
|
|
||||||
// eslint-disable-next-line sukka/no-redundant-variable -- self reference
|
// eslint-disable-next-line sukka/no-redundant-variable -- self reference
|
||||||
return span;
|
return span;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const dummySpan = createSpan('');
|
export const dummySpan = createSpan('');
|
||||||
|
|
||||||
export const task = (importMetaMain: boolean, importMetaPath: string) => <T>(fn: (span: Span) => Promise<T>, customName?: string) => {
|
export function task(importMetaMain: boolean, importMetaPath: string) {
|
||||||
const taskName = customName ?? basename(importMetaPath, extname(importMetaPath));
|
return <T>(fn: (span: Span) => Promise<T>, customName?: string) => {
|
||||||
|
const taskName = customName ?? basename(importMetaPath, extname(importMetaPath));
|
||||||
|
|
||||||
const dummySpan = createSpan(taskName);
|
const dummySpan = createSpan(taskName);
|
||||||
if (importMetaMain) {
|
if (importMetaMain) {
|
||||||
fn(dummySpan).finally(() => {
|
fn(dummySpan).finally(() => {
|
||||||
console.log(wtf.dump());
|
console.log(wtf.dump());
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return async (span?: Span) => {
|
|
||||||
if (span) {
|
|
||||||
return span.traceChildAsync(taskName, fn);
|
|
||||||
}
|
}
|
||||||
return fn(dummySpan);
|
|
||||||
|
return async (span?: Span) => {
|
||||||
|
if (span) {
|
||||||
|
return span.traceChildAsync(taskName, fn);
|
||||||
|
}
|
||||||
|
return fn(dummySpan);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
// const isSpan = (obj: any): obj is Span => {
|
// const isSpan = (obj: any): obj is Span => {
|
||||||
// return typeof obj === 'object' && obj && spanTag in obj;
|
// return typeof obj === 'object' && obj && spanTag in obj;
|
||||||
@ -128,10 +130,10 @@ export const task = (importMetaMain: boolean, importMetaPath: string) => <T>(fn:
|
|||||||
// };
|
// };
|
||||||
// };
|
// };
|
||||||
|
|
||||||
export const printTraceResult = (traceResult: TraceResult = rootTraceResult) => {
|
export function printTraceResult(traceResult: TraceResult = rootTraceResult) {
|
||||||
printStats(traceResult.children);
|
printStats(traceResult.children);
|
||||||
printTree(traceResult, node => `${node.name} ${picocolors.bold(`${(node.end - node.start).toFixed(3)}ms`)}`);
|
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 printTree(initialTree: TraceResult, printNode: (node: TraceResult, branch: string) => string) {
|
||||||
function printBranch(tree: TraceResult, branch: string, isGraphHead: boolean, isChildOfLastBranch: boolean) {
|
function printBranch(tree: TraceResult, branch: string, isGraphHead: boolean, isChildOfLastBranch: boolean) {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { parseFelixDnsmasq } from './lib/parse-dnsmasq';
|
|||||||
import { SOURCE_DIR } from './constants/dir';
|
import { SOURCE_DIR } from './constants/dir';
|
||||||
import { $fetch } from './lib/make-fetch-happen';
|
import { $fetch } from './lib/make-fetch-happen';
|
||||||
|
|
||||||
export const parseDomesticList = async () => {
|
export async function parseDomesticList() {
|
||||||
const trie = createTrie(await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf'));
|
const trie = createTrie(await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf'));
|
||||||
|
|
||||||
const top5000 = new Set<string>();
|
const top5000 = new Set<string>();
|
||||||
@ -60,4 +60,4 @@ export const parseDomesticList = async () => {
|
|||||||
// ]);
|
// ]);
|
||||||
|
|
||||||
console.log(notIncludedDomestic.size, notIncludedDomestic);
|
console.log(notIncludedDomestic.size, notIncludedDomestic);
|
||||||
};
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import path from 'node:path';
|
|||||||
import { SOURCE_DIR } from './constants/dir';
|
import { SOURCE_DIR } from './constants/dir';
|
||||||
import { $fetch } from './lib/make-fetch-happen';
|
import { $fetch } from './lib/make-fetch-happen';
|
||||||
|
|
||||||
export const parseGfwList = async () => {
|
export async function parseGfwList() {
|
||||||
const whiteSet = new Set<string>();
|
const whiteSet = new Set<string>();
|
||||||
const blackSet = new Set<string>();
|
const blackSet = new Set<string>();
|
||||||
|
|
||||||
@ -120,4 +120,4 @@ export const parseGfwList = async () => {
|
|||||||
trie,
|
trie,
|
||||||
top500Gfwed
|
top500Gfwed
|
||||||
] as const;
|
] as const;
|
||||||
};
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user