diff --git a/Build/build-common.ts b/Build/build-common.ts index 2e669975..d8b27a60 100644 --- a/Build/build-common.ts +++ b/Build/build-common.ts @@ -64,46 +64,48 @@ export const buildCommon = task(require.main === module, __filename)(async (span const $skip = Symbol('skip'); -const processFile = (span: Span, sourcePath: string) => span.traceChildAsync(`process file: ${sourcePath}`, async () => { - const lines: string[] = []; +function processFile(span: Span, sourcePath: string) { + return span.traceChildAsync(`process file: ${sourcePath}`, async () => { + const lines: string[] = []; - let title = ''; - const descriptions: string[] = []; - let sgmodulePathname: string | null = null; + let title = ''; + const descriptions: string[] = []; + let sgmodulePathname: string | null = null; - try { - for await (const line of readFileByLine(sourcePath)) { - if (line.startsWith(MAGIC_COMMAND_SKIP)) { - return $skip; - } - - 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; - } - - if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) { - sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.length).trim(); - continue; - } - - const l = processLine(line); - if (l) { - lines.push(l); + try { + for await (const line of readFileByLine(sourcePath)) { + if (line.startsWith(MAGIC_COMMAND_SKIP)) { + return $skip; + } + + 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; + } + + if (line.startsWith(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES)) { + sgmodulePathname = line.slice(MAGIC_COMMAND_SGMODULE_MITM_HOSTNAMES.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, sgmodulePathname] as const; -}); + return [title, descriptions, lines, sgmodulePathname] as const; + }); +} function transformDomainset(parentSpan: Span, sourcePath: string, relativePath: string) { return parentSpan diff --git a/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts b/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts index 1a3b6643..2d321ca1 100644 --- a/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts +++ b/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts @@ -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 { RulesetOutput } from './lib/create-file'; -const getRule = (domain: string) => { +function getRule(domain: string) { switch (domain[0]) { case '+': case '$': @@ -20,7 +20,7 @@ const getRule = (domain: string) => { default: return `DOMAIN-SUFFIX,${domain}`; } -}; +} export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => { const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf')); const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf')); diff --git a/Build/build-public.ts b/Build/build-public.ts index 0ff75ad3..defdbfae 100644 --- a/Build/build-public.ts +++ b/Build/build-public.ts @@ -14,7 +14,7 @@ import { compareAndWriteFile } from './lib/create-file'; const mockDir = path.join(ROOT_DIR, 'Mock'); const modulesDir = path.join(ROOT_DIR, 'Modules'); -const copyDirContents = async (srcDir: string, destDir: string) => { +async function copyDirContents(srcDir: string, destDir: string) { const promises: Array> = []; for await (const entry of await fsp.opendir(srcDir)) { @@ -28,7 +28,7 @@ const copyDirContents = async (srcDir: string, destDir: string) => { } return Promise.all(promises); -}; +} export const buildPublic = task(require.main === module, __filename)(async (span) => { 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 walk = (tree: TreeTypeArray) => { +function walk(tree: TreeTypeArray) { let result = ''; tree.sort(prioritySorter); for (let i = 0, len = tree.length; i < len; i++) { @@ -102,7 +102,7 @@ const walk = (tree: TreeTypeArray) => { } } return result; -}; +} function generateHtml(tree: TreeTypeArray) { return html` diff --git a/Build/build-speedtest-domainset.ts b/Build/build-speedtest-domainset.ts index 6e11b630..e3180c82 100644 --- a/Build/build-speedtest-domainset.ts +++ b/Build/build-speedtest-domainset.ts @@ -141,7 +141,7 @@ const latestTopUserAgentsPromise = $fetch('https://cdn.jsdelivr.net/npm/top-user .then(res => res.json()) .then((userAgents: string[]) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 '))); -const querySpeedtestApi = async (keyword: string): Promise> => { +async function querySpeedtestApi(keyword: string): Promise> { const topUserAgents = await latestTopUserAgentsPromise; 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> console.error(e); return []; } -}; +} export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => { const output = new DomainsetOutput(span, 'speedtest') diff --git a/Build/build-stream-service.ts b/Build/build-stream-service.ts index e69d7319..963008b7 100644 --- a/Build/build-stream-service.ts +++ b/Build/build-stream-service.ts @@ -6,33 +6,33 @@ import { ALL, NORTH_AMERICA, EU, HK, TW, JP, KR } from '../Source/stream'; import { SHARED_DESCRIPTION } from './lib/constants'; import { RulesetOutput } from './lib/create-file'; -export const createRulesetForStreamService = ( - span: Span, +export function createRulesetForStreamService(span: Span, fileId: string, title: string, - streamServices: Array -) => span.traceChildAsync(fileId, async (childSpan) => Promise.all([ + streamServices: Array) { + return span.traceChildAsync(fileId, async (childSpan) => Promise.all([ // Domains - new RulesetOutput(childSpan, fileId, 'non_ip') - .withTitle(`Sukka's Ruleset - Stream Services: ${title}`) - .withDescription([ - ...SHARED_DESCRIPTION, - '', - ...streamServices.map((i) => `- ${i.name}`) - ]) - .addFromRuleset(streamServices.flatMap((i) => i.rules)) - .write(), - // IP - new RulesetOutput(childSpan, fileId, 'ip') - .withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`) - .withDescription([ - ...SHARED_DESCRIPTION, - '', - ...streamServices.map((i) => `- ${i.name}`) - ]) - .bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? [])) - .bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? [])) - .write() -])); + new RulesetOutput(childSpan, fileId, 'non_ip') + .withTitle(`Sukka's Ruleset - Stream Services: ${title}`) + .withDescription([ + ...SHARED_DESCRIPTION, + '', + ...streamServices.map((i) => `- ${i.name}`) + ]) + .addFromRuleset(streamServices.flatMap((i) => i.rules)) + .write(), + // IP + new RulesetOutput(childSpan, fileId, 'ip') + .withTitle(`Sukka's Ruleset - Stream Services IPs: ${title}`) + .withDescription([ + ...SHARED_DESCRIPTION, + '', + ...streamServices.map((i) => `- ${i.name}`) + ]) + .bulkAddCIDR4NoResolve(streamServices.flatMap(i => i.ip?.v4 ?? [])) + .bulkAddCIDR6NoResolve(streamServices.flatMap(i => i.ip?.v6 ?? [])) + .write() + ])); +} export const buildStreamService = task(require.main === module, __filename)(async (span) => Promise.all([ createRulesetForStreamService(span, 'stream', 'All', ALL), diff --git a/Build/lib/aho-corasick.ts b/Build/lib/aho-corasick.ts index c1ab6984..86922e09 100644 --- a/Build/lib/aho-corasick.ts +++ b/Build/lib/aho-corasick.ts @@ -6,14 +6,14 @@ type Node = Map & { [FAIL]: Node | undefined }; -const createNode = (): Node => { +function createNode(): Node { const node = new Map() as Node; node[WORDEND] = false; node[FAIL] = undefined; return node; -}; +} -const createKeywordFilter = (keys: string[] | Set) => { +function createKeywordFilter(keys: string[] | Set) { const root = createNode(); // Create a trie with extra fields and information @@ -82,6 +82,6 @@ const createKeywordFilter = (keys: string[] | Set) => { return false; }; -}; +} export default createKeywordFilter; diff --git a/Build/lib/async-write-to-stream.ts b/Build/lib/async-write-to-stream.ts index 0c067ec5..64bee6f4 100644 --- a/Build/lib/async-write-to-stream.ts +++ b/Build/lib/async-write-to-stream.ts @@ -1,9 +1,9 @@ import type { Writable } from 'node:stream'; import { once } from 'node:events'; -export const asyncWriteToStream = (stream: Writable, chunk: T) => { +export function asyncWriteToStream(stream: Writable, chunk: T) { const res = stream.write(chunk); if (!res) { return once(stream, 'drain'); // returns a promise only if needed } -}; +} diff --git a/Build/lib/bitwise.ts b/Build/lib/bitwise.ts index 6ab868bc..363fed1f 100644 --- a/Build/lib/bitwise.ts +++ b/Build/lib/bitwise.ts @@ -2,11 +2,11 @@ export const pack = (a: number, b: number): number => (a << 16) | b; /** 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[1] = value & 0xFFFF; return arr; -}; +} export const unpackFirst = (value: number): number => (value >> 16) & 0xFFFF; export const unpackSecond = (value: number): number => value & 0xFFFF; diff --git a/Build/lib/cache-apply.ts b/Build/lib/cache-apply.ts index 8b7d410c..f516a60d 100644 --- a/Build/lib/cache-apply.ts +++ b/Build/lib/cache-apply.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -export const createCache = (namespace?: string, printStats = false) => { +export function createCache(namespace?: string, printStats = false) { const cache = new Map(); let hit = 0; @@ -30,4 +30,4 @@ export const createCache = (namespace?: string, printStats = false) => { return value; } }; -}; +} diff --git a/Build/lib/cache-filesystem.ts b/Build/lib/cache-filesystem.ts index 23416c69..d51989ab 100644 --- a/Build/lib/cache-filesystem.ts +++ b/Build/lib/cache-filesystem.ts @@ -410,7 +410,7 @@ export const serializeArray = (arr: string[]) => fastStringArrayJoin(arr, separa export const deserializeArray = (str: string) => str.split(separator); 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); return (key: string) => key + '$' + fileHash + '$'; -}; +} diff --git a/Build/lib/convert-clash-meta-mrs.ts b/Build/lib/convert-clash-meta-mrs.ts index 68060d5c..f2beb827 100644 --- a/Build/lib/convert-clash-meta-mrs.ts +++ b/Build/lib/convert-clash-meta-mrs.ts @@ -22,7 +22,7 @@ const mihomoBinaryUrl: Partial { +async function ensureMihomoBinary() { await mkdirp(mihomoBinaryDir); if (!fs.existsSync(mihomoBinaryPath)) { const writeStream = fs.createWriteStream(mihomoBinaryPath); @@ -47,9 +47,9 @@ const ensureMihomoBinary = async () => { ); } 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(); 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) { throw new Error(stderr); } -}; +} diff --git a/Build/lib/create-file.test.ts b/Build/lib/create-file.test.ts index 5df73964..8fd26eee 100644 --- a/Build/lib/create-file.test.ts +++ b/Build/lib/create-file.test.ts @@ -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); -}; +} describe('fileEqual', () => { it('same', () => test( diff --git a/Build/lib/fetch-assets.ts b/Build/lib/fetch-assets.ts index 7b2b97f6..804d1654 100644 --- a/Build/lib/fetch-assets.ts +++ b/Build/lib/fetch-assets.ts @@ -26,19 +26,21 @@ export class CustomNoETagFallbackError extends Error { } } -export const sleepWithAbort = (ms: number, signal: AbortSignal) => new Promise((resolve, reject) => { - if (signal.aborted) { - reject(signal.reason as Error); - return; - } +export function sleepWithAbort(ms: number, signal: AbortSignal) { + return new Promise((resolve, reject) => { + if (signal.aborted) { + 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 - setTimeout(ms, undefined, { ref: false }).then(resolve).catch(reject).finally(() => signal.removeEventListener('abort', stop)); + // 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)); - 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[]) { const controller = new AbortController(); diff --git a/Build/lib/fetch-retry.ts b/Build/lib/fetch-retry.ts index 373b436d..8c6471c7 100644 --- a/Build/lib/fetch-retry.ts +++ b/Build/lib/fetch-retry.ts @@ -95,26 +95,6 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry { return res; } } 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)) { return bail(err) as never; }; @@ -140,6 +120,26 @@ function createFetchRetry($fetch: typeof fetch): FetchWithRetry { throw newErr; } }, 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) { if (err instanceof ResponseError) { return err.res; diff --git a/Build/lib/fetch-text-by-line.ts b/Build/lib/fetch-text-by-line.ts index cb3b5632..604b701d 100644 --- a/Build/lib/fetch-text-by-line.ts +++ b/Build/lib/fetch-text-by-line.ts @@ -9,19 +9,19 @@ import { processLine } from './process-line'; import { $fetch } 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') { // return fs.openAsBlob(file).then(blob => blob.stream()) return Readable.toWeb(fs.createReadStream(file/* , { encoding: 'utf-8' } */)); } return file.readableWebStream(); -}; +} // TODO: use FileHandle.readLine() export const readFileByLine: ((file: string | FileHandle) => AsyncIterable) = (file: string | FileHandle) => getReadableStream(file) .pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()); -const ensureResponseBody = (resp: T): NonNullable => { +function ensureResponseBody(resp: T): NonNullable { if (!resp.body) { throw new Error('Failed to fetch remote text'); } @@ -29,7 +29,7 @@ const ensureResponseBody = (resp: T): No throw new Error('Body has already been consumed.'); } return resp.body; -}; +} export const createReadlineInterfaceFromResponse: ((resp: Response | NodeFetchResponse) => AsyncIterable) = (resp) => { const stream = ensureResponseBody(resp); diff --git a/Build/lib/get-phishing-domains.ts b/Build/lib/get-phishing-domains.ts index ade41377..554a0b2c 100644 --- a/Build/lib/get-phishing-domains.ts +++ b/Build/lib/get-phishing-domains.ts @@ -101,25 +101,27 @@ const lowKeywords = createKeywordFilter([ const cacheKey = createCacheKey(__filename); -export const getPhishingDomains = (parentSpan: Span) => parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => { - const domainArr = await span.traceChildAsync('download/parse/merge phishing domains', async (curSpan) => { - const domainArr: string[] = []; +export function getPhishingDomains(parentSpan: Span) { + return parentSpan.traceChild('get phishing domains').traceAsyncFn(async (span) => { + 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)))) - .forEach(appendArrayInPlaceCurried(domainArr)); - (await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey)))) - .forEach(appendArrayInPlaceCurried(domainArr)); + (await Promise.all(PHISHING_DOMAIN_LISTS_EXTRA.map(entry => processDomainLists(curSpan, ...entry, cacheKey)))) + .forEach(appendArrayInPlaceCurried(domainArr)); + (await Promise.all(PHISHING_HOSTS_EXTRA.map(entry => processHosts(curSpan, ...entry, cacheKey)))) + .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 = '') { return fsFetchCache.apply( diff --git a/Build/lib/memo-promise.ts b/Build/lib/memo-promise.ts index f76d05bc..04e1f8a8 100644 --- a/Build/lib/memo-promise.ts +++ b/Build/lib/memo-promise.ts @@ -1,6 +1,6 @@ const notError = Symbol('notError'); -export const createMemoizedPromise = (fn: () => Promise, preload = true): () => Promise => { +export function createMemoizedPromise(fn: () => Promise, preload = true): () => Promise { let error: Error | typeof notError = notError; let promise: Promise | null = preload @@ -19,4 +19,4 @@ export const createMemoizedPromise = (fn: () => Promise, preload = true): promise ??= fn(); return promise; }; -}; +} diff --git a/Build/lib/misc.ts b/Build/lib/misc.ts index b364064a..11d79d8b 100644 --- a/Build/lib/misc.ts +++ b/Build/lib/misc.ts @@ -5,7 +5,7 @@ import { OUTPUT_CLASH_DIR, OUTPUT_SINGBOX_DIR, OUTPUT_SURGE_DIR } from '../const export const isTruthy = (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 = ''; for (let i = 0, len = arr.length; i < len; i++) { if (i !== 0) { @@ -14,7 +14,7 @@ export const fastStringArrayJoin = (arr: string[], sep: string) => { result += arr[i]; } return result; -}; +} interface Write { ( @@ -23,12 +23,12 @@ interface Write { ): Promise } -export const mkdirp = (dir: string) => { +export function mkdirp(dir: string) { if (fs.existsSync(dir)) { return; } return fsp.mkdir(dir, { recursive: true }); -}; +} export const writeFile: Write = async (destination: string, input, dir = dirname(destination)) => { 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 domainWildCardToRegex = (domain: string) => { +export function domainWildCardToRegex(domain: string) { let result = '^'; for (let i = 0, len = domain.length; i < len; i++) { switch (domain[i]) { @@ -59,11 +59,11 @@ export const domainWildCardToRegex = (domain: string) => { } result += '$'; return result; -}; +} export const identity = (x: T): T => x; -export const appendArrayFromSet = (dest: T[], source: Set | Array>, transformer: (item: T) => T = identity) => { +export function appendArrayFromSet(dest: T[], source: Set | Array>, transformer: (item: T) => T = identity) { const casted = Array.isArray(source) ? source : [source]; for (let i = 0, len = casted.length; i < len; i++) { const iterator = casted[i].values(); @@ -75,13 +75,15 @@ export const appendArrayFromSet = (dest: T[], source: Set | Array>, } return dest; -}; +} -export const output = (id: string, type: 'non_ip' | 'ip' | 'domainset') => [ - path.join(OUTPUT_SURGE_DIR, type, id + '.conf'), - path.join(OUTPUT_CLASH_DIR, type, id + '.txt'), - path.join(OUTPUT_SINGBOX_DIR, type, id + '.json') -] as const; +export function output(id: string, type: 'non_ip' | 'ip' | 'domainset') { + return [ + path.join(OUTPUT_SURGE_DIR, type, id + '.conf'), + path.join(OUTPUT_CLASH_DIR, type, id + '.txt'), + path.join(OUTPUT_SINGBOX_DIR, type, id + '.json') + ] as const; +} export function withBannerArray(title: string, description: string[] | readonly string[], date: Date, content: string[]) { 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) { return headersB; } @@ -116,9 +118,9 @@ export const mergeHeaders = (headersA: RequestInit['headers'] | undefined, heade for (const key in headersB) { if (Object.hasOwn(headersB, key)) { - result.set(key, (headersB as Record)[key]); + result.set(key, (headersB)[key]); } } return result; -}; +} diff --git a/Build/lib/normalize-domain.ts b/Build/lib/normalize-domain.ts index 6da49dbc..56b5dd77 100644 --- a/Build/lib/normalize-domain.ts +++ b/Build/lib/normalize-domain.ts @@ -5,7 +5,7 @@ import { normalizeTldtsOpt } from '../constants/loose-tldts-opt'; type TldTsParsed = ReturnType; -export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = null) => { +export function normalizeDomain(domain: string, parsed: TldTsParsed | null = null) { if (domain.length === 0) return null; parsed ??= tldts.parse(domain, normalizeTldtsOpt); @@ -29,4 +29,4 @@ export const normalizeDomain = (domain: string, parsed: TldTsParsed | null = nul } return h.length > 0 ? h : null; -}; +} diff --git a/Build/lib/parse-dnsmasq.ts b/Build/lib/parse-dnsmasq.ts index a7f4d86d..967c0b11 100644 --- a/Build/lib/parse-dnsmasq.ts +++ b/Build/lib/parse-dnsmasq.ts @@ -3,19 +3,19 @@ import { parse as tldtsParse } from 'tldts'; import { $fetch } 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); 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')) { return line.slice(8, -16); } return null; -}; +} -export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchResponse): Promise => { +export async function parseFelixDnsmasqFromResp(resp: Response | NodeFetchResponse): Promise { const results: string[] = []; for await (const line of createReadlineInterfaceFromResponse(resp)) { @@ -26,9 +26,9 @@ export const parseFelixDnsmasqFromResp = async (resp: Response | NodeFetchRespon } return results; -}; +} -export const parseFelixDnsmasq = async (url: string): Promise => { +export async function parseFelixDnsmasq(url: string): Promise { const resp = await $fetch(url); return parseFelixDnsmasqFromResp(resp); -}; +} diff --git a/Build/lib/parse-filter.ts b/Build/lib/parse-filter.ts index 91699b09..71e64a18 100644 --- a/Build/lib/parse-filter.ts +++ b/Build/lib/parse-filter.ts @@ -15,7 +15,7 @@ const DEBUG_DOMAIN_TO_FIND: string | null = null; // example.com | null let foundDebugDomain = false; 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); if (!line) return; line = line.toLowerCase(); @@ -39,7 +39,7 @@ const domainListLineCb = (l: string, set: string[], includeAllSubDomain: boolean } set.push(includeAllSubDomain ? `.${line}` : line); -}; +} export function processDomainLists( 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); if (!line) { return; @@ -91,7 +91,7 @@ const hostsLineCb = (l: string, set: string[], includeAllSubDomain: boolean, met } set.push(includeAllSubDomain ? `.${domain}` : domain); -}; +} export function processHosts( span: Span, diff --git a/Build/lib/process-line.ts b/Build/lib/process-line.ts index 4785a66a..76fcd617 100644 --- a/Build/lib/process-line.ts +++ b/Build/lib/process-line.ts @@ -1,4 +1,4 @@ -export const processLine = (line: string): string | null => { +export function processLine(line: string): string | null { if (!line) { return null; } @@ -22,9 +22,9 @@ export const processLine = (line: string): string | null => { } return trimmed; -}; +} -export const processLineFromReadline = async (rl: AsyncIterable): Promise => { +export async function processLineFromReadline(rl: AsyncIterable): Promise { const res: string[] = []; for await (const line of rl) { const l: string | null = processLine(line); @@ -33,4 +33,4 @@ export const processLineFromReadline = async (rl: AsyncIterable): Promis } } return res; -}; +} diff --git a/Build/lib/rules/base.ts b/Build/lib/rules/base.ts index 17bae95e..8e88bb35 100644 --- a/Build/lib/rules/base.ts +++ b/Build/lib/rules/base.ts @@ -125,6 +125,7 @@ export abstract class RuleOutput { } private async addFromDomainsetPromise(source: AsyncIterable | 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) { if (line[0] === '.') { this.addDomainSuffix(line); @@ -140,6 +141,7 @@ export abstract class RuleOutput { } private async addFromRulesetPromise(source: AsyncIterable | Iterable) { + // eslint-disable-next-line @typescript-eslint/await-thenable -- https://github.com/typescript-eslint/typescript-eslint/issues/10080 for await (const line of source) { const splitted = line.split(','); const type = splitted[0]; @@ -314,7 +316,7 @@ export abstract class RuleOutput { abstract mitmSgmodule?(): string[] | null; } -export const fileEqual = async (linesA: string[], source: AsyncIterable): Promise => { +export async function fileEqual(linesA: string[], source: AsyncIterable): Promise { if (linesA.length === 0) { return false; } @@ -350,7 +352,7 @@ export const fileEqual = async (linesA: string[], source: AsyncIterable) // The file becomes larger return !(index < linesA.length - 1); -}; +} export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) { let isEqual = true; diff --git a/Build/lib/singbox.ts b/Build/lib/singbox.ts index 4e5d26a0..5b1edf22 100644 --- a/Build/lib/singbox.ts +++ b/Build/lib/singbox.ts @@ -2,10 +2,10 @@ import { isProbablyIpv4, isProbablyIpv6 } from './is-fast-ip'; const unsupported = Symbol('unsupported'); -const toNumberTuple = (key: T, value: string): [T, number] | null => { +function toNumberTuple(key: T, value: string): [T, number] | null { const tmp = Number(value); return Number.isNaN(tmp) ? null : [key, tmp]; -}; +} // https://sing-box.sagernet.org/configuration/rule-set/source-format/ export const PROCESSOR: Record [key: keyof SingboxHeadlessRule, value: Required[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = { diff --git a/Build/lib/stable-sort-domain.ts b/Build/lib/stable-sort-domain.ts index ac346c98..bb3d64af 100644 --- a/Build/lib/stable-sort-domain.ts +++ b/Build/lib/stable-sort-domain.ts @@ -4,12 +4,12 @@ import * as tldts from 'tldts-experimental'; 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; return (a.length - b.length) || a.localeCompare(b); -}; +} -export const buildParseDomainMap = (inputs: string[]) => { +export function buildParseDomainMap(inputs: string[]) { const domainMap = new Map(); const subdomainMap = new Map(); @@ -24,13 +24,11 @@ export const buildParseDomainMap = (inputs: string[]) => { } return { domainMap, subdomainMap }; -}; +} -export const sortDomains = ( - inputs: string[], +export function sortDomains(inputs: string[], domainMap?: Map | null, - subdomainMap?: Map | null -) => { + subdomainMap?: Map | null) { if (!domainMap || !subdomainMap) { const { domainMap: dm, subdomainMap: sm } = buildParseDomainMap(inputs); domainMap = dm; @@ -58,4 +56,4 @@ export const sortDomains = ( }; return inputs.sort(sorter); -}; +} diff --git a/Build/lib/string-hash.ts b/Build/lib/string-hash.ts index 457e846a..8084201e 100644 --- a/Build/lib/string-hash.ts +++ b/Build/lib/string-hash.ts @@ -7,7 +7,7 @@ * 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. */ -export const fnv1a52 = (str: string) => { +export function fnv1a52(str: string) { const len = str.length; let i = 0, t0 = 0, @@ -41,6 +41,6 @@ export const fnv1a52 = (str: string) => { + v1 * 65536 + (v0 ^ (v3 >> 4)) ); -}; +} export const stringHash = (payload: string) => fnv1a52(payload).toString(36) + payload.length.toString(36); diff --git a/Build/lib/tree-dir.ts b/Build/lib/tree-dir.ts index c02c69ec..545baf6d 100644 --- a/Build/lib/tree-dir.ts +++ b/Build/lib/tree-dir.ts @@ -19,7 +19,7 @@ export type TreeTypeArray = TreeType[]; type VoidOrVoidArray = void | VoidOrVoidArray[]; -export const treeDir = async (rootPath: string): Promise => { +export async function treeDir(rootPath: string): Promise { const tree: TreeTypeArray = []; const walk = async (dir: string, node: TreeTypeArray, dirRelativeToRoot = ''): Promise => { @@ -60,4 +60,4 @@ export const treeDir = async (rootPath: string): Promise => { await walk(rootPath, tree); return tree; -}; +} diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 064fff6b..5737f63d 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -13,10 +13,8 @@ type TrieNode = [ Meta /** meta */ ]; -const deepTrieNodeToJSON = ( - node: TrieNode, - unpackMeta: ((meta?: any) => string) | undefined -) => { +function deepTrieNodeToJSON(node: TrieNode, + unpackMeta: ((meta?: any) => string) | undefined) { const obj: Record = {}; if (node[0]) { obj['[start]'] = node[0]; @@ -32,11 +30,11 @@ const deepTrieNodeToJSON = ( obj[key] = deepTrieNodeToJSON(value, unpackMeta); }); return obj; -}; +} const createNode = (parent: TrieNode | null = null): TrieNode => [false, parent, new Map(), null] as TrieNode; -export const hostnameToTokens = (hostname: string): string[] => { +export function hostnameToTokens(hostname: string): string[] { const tokens = hostname.split('.'); const results: string[] = []; let token = ''; @@ -51,9 +49,9 @@ export const hostnameToTokens = (hostname: string): string[] => { } } 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('.'); let token = ''; @@ -78,7 +76,7 @@ const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolea } return false; -}; +} interface FindSingleChildLeafResult { node: TrieNode, diff --git a/Build/trace/index.ts b/Build/trace/index.ts index 7d831e99..da6907f7 100644 --- a/Build/trace/index.ts +++ b/Build/trace/index.ts @@ -34,7 +34,7 @@ export interface Span { readonly traceResult: TraceResult } -export const createSpan = (name: string, parentTraceResult?: TraceResult): Span => { +export function createSpan(name: string, parentTraceResult?: TraceResult): Span { const start = performance.now(); 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 return span; -}; +} export const dummySpan = createSpan(''); -export const task = (importMetaMain: boolean, importMetaPath: string) => (fn: (span: Span) => Promise, customName?: string) => { - const taskName = customName ?? basename(importMetaPath, extname(importMetaPath)); +export function task(importMetaMain: boolean, importMetaPath: string) { + return (fn: (span: Span) => Promise, customName?: string) => { + const taskName = customName ?? basename(importMetaPath, extname(importMetaPath)); - const dummySpan = createSpan(taskName); - if (importMetaMain) { - fn(dummySpan).finally(() => { - console.log(wtf.dump()); - }); - } - - return async (span?: Span) => { - if (span) { - return span.traceChildAsync(taskName, fn); + const dummySpan = createSpan(taskName); + if (importMetaMain) { + fn(dummySpan).finally(() => { + console.log(wtf.dump()); + }); } - return fn(dummySpan); + + return async (span?: Span) => { + if (span) { + return span.traceChildAsync(taskName, fn); + } + return fn(dummySpan); + }; }; -}; +} // const isSpan = (obj: any): obj is Span => { // return typeof obj === 'object' && obj && spanTag in obj; @@ -128,10 +130,10 @@ export const task = (importMetaMain: boolean, importMetaPath: string) => (fn: // }; // }; -export const printTraceResult = (traceResult: TraceResult = rootTraceResult) => { +export function 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, isGraphHead: boolean, isChildOfLastBranch: boolean) { diff --git a/Build/validate-domestic.ts b/Build/validate-domestic.ts index b48ce06b..dcd2adfa 100644 --- a/Build/validate-domestic.ts +++ b/Build/validate-domestic.ts @@ -7,7 +7,7 @@ import { parseFelixDnsmasq } from './lib/parse-dnsmasq'; import { SOURCE_DIR } from './constants/dir'; 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 top5000 = new Set(); @@ -60,4 +60,4 @@ export const parseDomesticList = async () => { // ]); console.log(notIncludedDomestic.size, notIncludedDomestic); -}; +} diff --git a/Build/validate-gfwlist.ts b/Build/validate-gfwlist.ts index 9f9b5804..72681acf 100644 --- a/Build/validate-gfwlist.ts +++ b/Build/validate-gfwlist.ts @@ -8,7 +8,7 @@ import path from 'node:path'; import { SOURCE_DIR } from './constants/dir'; import { $fetch } from './lib/make-fetch-happen'; -export const parseGfwList = async () => { +export async function parseGfwList() { const whiteSet = new Set(); const blackSet = new Set(); @@ -120,4 +120,4 @@ export const parseGfwList = async () => { trie, top500Gfwed ] as const; -}; +}