Rrefactor: rewrite trie in class

This commit is contained in:
SukkaW 2024-10-02 22:01:38 +08:00
parent d1041f0e59
commit a7e7c19a51
7 changed files with 187 additions and 185 deletions

View File

@ -10,19 +10,16 @@ import { processLine } from './lib/process-line';
import { DomainsetOutput } from './lib/create-file'; import { DomainsetOutput } from './lib/create-file';
const getS3OSSDomainsPromise = (async (): Promise<string[]> => { const getS3OSSDomainsPromise = (async (): Promise<string[]> => {
const trie = createTrie( const trie = createTrie((await getPublicSuffixListTextPromise()).reduce<string[]>(
(await getPublicSuffixListTextPromise()).reduce<string[]>( (acc, cur) => {
(acc, cur) => { const tmp = processLine(cur);
const tmp = processLine(cur); if (tmp) {
if (tmp) { acc.push(tmp);
acc.push(tmp); }
} return acc;
return acc; },
}, []
[] ));
),
true
);
/** /**
* Extract OSS domain from publicsuffix list * Extract OSS domain from publicsuffix list

View File

@ -1,6 +1,6 @@
import { task } from './trace'; import { task } from './trace';
import { fetchRemoteTextByLine } from './lib/fetch-text-by-line'; import { fetchRemoteTextByLine } from './lib/fetch-text-by-line';
import { createTrie } from './lib/trie'; import { HostnameSmolTrie } from './lib/trie';
import { SHARED_DESCRIPTION } from './lib/constants'; import { SHARED_DESCRIPTION } from './lib/constants';
import { createMemoizedPromise } from './lib/memo-promise'; import { createMemoizedPromise } from './lib/memo-promise';
import { extractDomainsFromFelixDnsmasq } from './lib/parse-dnsmasq'; import { extractDomainsFromFelixDnsmasq } from './lib/parse-dnsmasq';
@ -27,7 +27,7 @@ const BLACKLIST = [
export const getMicrosoftCdnRulesetPromise = createMemoizedPromise<[domains: string[], domainSuffixes: string[]]>(async () => { export const getMicrosoftCdnRulesetPromise = createMemoizedPromise<[domains: string[], domainSuffixes: string[]]>(async () => {
// First trie is to find the microsoft domains that matches probe domains // First trie is to find the microsoft domains that matches probe domains
const trie = createTrie(null, true); const trie = new HostnameSmolTrie();
for await (const line of await fetchRemoteTextByLine('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf')) { for await (const line of await fetchRemoteTextByLine('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf')) {
const domain = extractDomainsFromFelixDnsmasq(line); const domain = extractDomainsFromFelixDnsmasq(line);
if (domain) { if (domain) {
@ -37,8 +37,8 @@ export const getMicrosoftCdnRulesetPromise = createMemoizedPromise<[domains: str
const foundMicrosoftCdnDomains = PROBE_DOMAINS.flatMap(domain => trie.find(domain)); const foundMicrosoftCdnDomains = PROBE_DOMAINS.flatMap(domain => trie.find(domain));
// Second trie is to remove blacklisted domains // Second trie is to remove blacklisted domains
const trie2 = createTrie(foundMicrosoftCdnDomains, true); const trie2 = new HostnameSmolTrie(foundMicrosoftCdnDomains);
BLACKLIST.forEach(trie2.whitelist); BLACKLIST.forEach(black => trie2.whitelist(black));
const domains: string[] = DOMAINS; const domains: string[] = DOMAINS;
const domainSuffixes: string[] = DOMAIN_SUFFIXES; const domainSuffixes: string[] = DOMAIN_SUFFIXES;

View File

@ -191,7 +191,7 @@ async function processPhihsingDomains(domainArr: string[]) {
); );
} }
export function calcDomainAbuseScore(subdomain: string, fullDomain: string) { export function calcDomainAbuseScore(subdomain: string, fullDomain: string = subdomain) {
let weight = 0; let weight = 0;
const hitLowKeywords = lowKeywords(fullDomain); const hitLowKeywords = lowKeywords(fullDomain);

View File

@ -56,6 +56,7 @@ describe('Trie', () => {
trie.add('skk.moe'); trie.add('skk.moe');
trie.add('blog.skk.moe'); trie.add('blog.skk.moe');
// eslint-disable-next-line sukka/no-element-overwrite -- deliberately do testing
trie.add('skk.moe'); trie.add('skk.moe');
expect(trie.size).to.equal(2); expect(trie.size).to.equal(2);
@ -63,18 +64,18 @@ describe('Trie', () => {
}); });
it('should be possible to set the null sequence.', () => { it('should be possible to set the null sequence.', () => {
let trie = createTrie(null, false); const trie = createTrie(null, false);
trie.add(''); trie.add('');
expect(trie.has('')).to.equal(true); expect(trie.has('')).to.equal(true);
trie = createTrie(null, true); const trie2 = createTrie(null, true);
trie.add(''); trie2.add('');
expect(trie.has('')).to.equal(true); expect(trie2.has('')).to.equal(true);
}); });
it('should be possible to delete items.', () => { it('should be possible to delete items.', () => {
const trie = createTrie(null); const trie = createTrie(null, false);
trie.add('skk.moe'); trie.add('skk.moe');
trie.add('example.com'); trie.add('example.com');
@ -108,7 +109,7 @@ describe('Trie', () => {
}); });
it('should be possible to retrieve items matching the given prefix.', () => { it('should be possible to retrieve items matching the given prefix.', () => {
const trie = createTrie(null); const trie = createTrie(null, false);
trie.add('example.com'); trie.add('example.com');
trie.add('blog.example.com'); trie.add('blog.example.com');
@ -141,12 +142,12 @@ describe('Trie', () => {
}); });
it('should be possible to create a trie from an arbitrary iterable.', () => { it('should be possible to create a trie from an arbitrary iterable.', () => {
let trie = createTrie(['skk.moe', 'blog.skk.moe']); let trie = createTrie(['skk.moe', 'blog.skk.moe'], false);
expect(trie.size).to.equal(2); expect(trie.size).to.equal(2);
expect(trie.has('skk.moe')).to.equal(true); expect(trie.has('skk.moe')).to.equal(true);
trie = createTrie(new Set(['skk.moe', 'example.com'])); trie = createTrie(new Set(['skk.moe', 'example.com']), false);
expect(trie.size).to.equal(2); expect(trie.size).to.equal(2);
expect(trie.has('skk.moe')).to.equal(true); expect(trie.has('skk.moe')).to.equal(true);
}); });
@ -154,28 +155,28 @@ describe('Trie', () => {
describe('surge domainset dedupe', () => { describe('surge domainset dedupe', () => {
it('should not remove same entry', () => { it('should not remove same entry', () => {
const trie = createTrie(['.skk.moe', 'noc.one']); const trie = createTrie(['.skk.moe', 'noc.one'], false);
expect(trie.find('.skk.moe')).to.deep.equal(['.skk.moe']); expect(trie.find('.skk.moe')).to.deep.equal(['.skk.moe']);
expect(trie.find('noc.one')).to.deep.equal(['noc.one']); expect(trie.find('noc.one')).to.deep.equal(['noc.one']);
}); });
it('should match subdomain - 1', () => { it('should match subdomain - 1', () => {
const trie = createTrie(['www.noc.one', 'www.sukkaw.com', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net']); const trie = createTrie(['www.noc.one', 'www.sukkaw.com', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net'], false);
expect(trie.find('.skk.moe')).to.deep.equal(['image.cdn.skk.moe', 'blog.skk.moe']); expect(trie.find('.skk.moe')).to.deep.equal(['image.cdn.skk.moe', 'blog.skk.moe']);
expect(trie.find('.sukkaw.com')).to.deep.equal(['www.sukkaw.com']); expect(trie.find('.sukkaw.com')).to.deep.equal(['www.sukkaw.com']);
}); });
it('should match subdomain - 2', () => { it('should match subdomain - 2', () => {
const trie = createTrie(['www.noc.one', 'www.sukkaw.com', '.skk.moe', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net']); const trie = createTrie(['www.noc.one', 'www.sukkaw.com', '.skk.moe', 'blog.skk.moe', 'image.cdn.skk.moe', 'cdn.sukkaw.net'], false);
expect(trie.find('.skk.moe')).to.deep.equal(['.skk.moe', 'image.cdn.skk.moe', 'blog.skk.moe']); expect(trie.find('.skk.moe')).to.deep.equal(['.skk.moe', 'image.cdn.skk.moe', 'blog.skk.moe']);
expect(trie.find('.sukkaw.com')).to.deep.equal(['www.sukkaw.com']); expect(trie.find('.sukkaw.com')).to.deep.equal(['www.sukkaw.com']);
}); });
it('should not remove non-subdomain', () => { it('should not remove non-subdomain', () => {
const trie = createTrie(['skk.moe', 'sukkaskk.moe']); const trie = createTrie(['skk.moe', 'sukkaskk.moe'], false);
expect(trie.find('.skk.moe')).to.deep.equal([]); expect(trie.find('.skk.moe')).to.deep.equal([]);
}); });
}); });

View File

@ -80,94 +80,39 @@ const walkHostnameTokens = (hostname: string, onToken: (token: string) => boolea
return false; return false;
}; };
export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smolTree = false) => { interface FindSingleChildLeafResult<Meta> {
let size = 0; node: TrieNode<Meta>,
const root: TrieNode<Meta> = createNode(); toPrune: TrieNode<Meta> | null,
tokenToPrune: string | null,
parent: TrieNode<Meta>
}
/** abstract class Triebase<Meta = any> {
* Method used to add the given suffix to the trie. protected readonly $root: TrieNode<Meta> = createNode();
*/ protected $size = 0;
const add = smolTree
? (suffix: string, meta?: Meta): void => {
let node: TrieNode<Meta> = root;
let curNodeChildren: Map<string, TrieNode<Meta>> = node[2];
const onToken = (token: string) => { get root() {
curNodeChildren = node[2]; return this.$root;
if (curNodeChildren.has(token)) { }
node = curNodeChildren.get(token)!;
// During the adding of `[start]blog|.skk.moe` and find out that there is a `[start].skk.moe` in the trie, skip adding the rest of the node constructor(from?: string[] | Set<string> | null) {
if (node[0] && token === '.') { // Actually build trie
return true; if (Array.isArray(from)) {
} for (let i = 0, l = from.length; i < l; i++) {
} else { this.add(from[i]);
const newNode = createNode(node);
curNodeChildren.set(token, newNode);
node = newNode;
}
return false;
};
// When walkHostnameTokens returns true, we should skip the rest
if (walkHostnameTokens(suffix, onToken)) {
return;
} }
} else if (from) {
// If we are in smolTree mode, we need to do something at the end of the loop from.forEach((value) => this.add(value));
if (suffix[0] === '.') {
// Trying to add `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
// Make sure parent `[start]sub.example.com` (without dot) is removed (SETINEL to false)
(/** parent */ node[1]!)[0] = false;
// Removing the rest of the parent's child nodes
node[2].clear();
// The SENTINEL of this node will be set to true at the end of the function, so we don't need to set it here
// we can use else-if here, because the children is now empty, we don't need to check the leading "."
} else if (node[2].get('.')?.[0] === true) {
// Trying to add `example.com` when there is already a `.example.com` in the trie
// No need to increment size and set SENTINEL to true (skip this "new" item)
return;
}
node[0] = true;
node[3] = meta!;
} }
: (suffix: string, meta?: Meta): void => { }
let node: TrieNode<Meta> = root;
const onToken = (token: string) => { public abstract add(suffix: string, meta?: Meta): void;
if (node[2].has(token)) {
node = node[2].get(token)!;
} else {
const newNode = createNode(node);
node[2].set(token, newNode);
node = newNode;
}
return false; protected walkIntoLeafWithTokens(
};
// When walkHostnameTokens returns true, we should skip the rest
if (walkHostnameTokens(suffix, onToken)) {
return;
}
if (!node[0]) {
size++;
node[0] = true;
node[3] = meta!;
}
};
const walkIntoLeafWithTokens = (
tokens: string[], tokens: string[],
onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
) => { ) {
let node: TrieNode = root; let node: TrieNode = this.$root;
let parent: TrieNode = node; let parent: TrieNode = node;
let token: string; let token: string;
@ -193,11 +138,11 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
return { node, parent }; return { node, parent };
}; };
const walkIntoLeafWithSuffix = ( protected walkIntoLeafWithSuffix(
suffix: string, suffix: string,
onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
) => { ) {
let node: TrieNode = root; let node: TrieNode = this.$root;
let parent: TrieNode = node; let parent: TrieNode = node;
const onToken = (token: string) => { const onToken = (token: string) => {
@ -225,18 +170,18 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
return { node, parent }; return { node, parent };
}; };
const contains = (suffix: string): boolean => walkIntoLeafWithSuffix(suffix) !== null; public contains(suffix: string): boolean { return this.walkIntoLeafWithSuffix(suffix) !== null; };
const walk = ( private walk(
onMatches: (suffix: string[], meta: Meta) => void, onMatches: (suffix: string[], meta: Meta) => void,
initialNode = root, initialNode = this.$root,
initialSuffix: string[] = [] initialSuffix: string[] = []
) => { ) {
const nodeStack: Array<TrieNode<Meta>> = [initialNode]; const nodeStack: Array<TrieNode<Meta>> = [initialNode];
// Resolving initial string (begin the start of the stack) // Resolving initial string (begin the start of the stack)
const suffixStack: string[][] = [initialSuffix]; const suffixStack: string[][] = [initialSuffix];
let node: TrieNode<Meta> = root; let node: TrieNode<Meta> = initialNode;
do { do {
node = nodeStack.pop()!; node = nodeStack.pop()!;
@ -256,14 +201,7 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
} while (nodeStack.length); } while (nodeStack.length);
}; };
interface FindSingleChildLeafResult { protected getSingleChildLeaf(tokens: string[]): FindSingleChildLeafResult<Meta> | null {
node: TrieNode,
toPrune: TrieNode | null,
tokenToPrune: string | null,
parent: TrieNode
}
const getSingleChildLeaf = (tokens: string[]): FindSingleChildLeafResult | null => {
let toPrune: TrieNode | null = null; let toPrune: TrieNode | null = null;
let tokenToPrune: string | null = null; let tokenToPrune: string | null = null;
@ -289,7 +227,7 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
} }
}; };
const res = walkIntoLeafWithTokens(tokens, onLoop); const res = this.walkIntoLeafWithTokens(tokens, onLoop);
if (res === null) return null; if (res === null) return null;
return { node: res.node, toPrune, tokenToPrune, parent: res.parent }; return { node: res.node, toPrune, tokenToPrune, parent: res.parent };
@ -298,16 +236,16 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
/** /**
* Method used to retrieve every item in the trie with the given prefix. * Method used to retrieve every item in the trie with the given prefix.
*/ */
const find = ( public find(
inputSuffix: string, inputSuffix: string,
/** @default true */ includeEqualWithSuffix = true /** @default true */ includeEqualWithSuffix = true
): string[] => { ): string[] {
// if (smolTree) { // if (smolTree) {
// throw new Error('A Trie with smolTree enabled cannot perform find!'); // throw new Error('A Trie with smolTree enabled cannot perform find!');
// } // }
const inputTokens = hostnameToTokens(inputSuffix); const inputTokens = hostnameToTokens(inputSuffix);
const res = walkIntoLeafWithTokens(inputTokens); const res = this.walkIntoLeafWithTokens(inputTokens);
if (res === null) return []; if (res === null) return [];
const matches: string[][] = []; const matches: string[][] = [];
@ -322,7 +260,7 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
} }
}; };
walk( this.walk(
onMatches, onMatches,
res.node, // Performing DFS from prefix res.node, // Performing DFS from prefix
inputTokens inputTokens
@ -334,13 +272,13 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
/** /**
* Method used to delete a prefix from the trie. * Method used to delete a prefix from the trie.
*/ */
const remove = (suffix: string): boolean => { public remove(suffix: string): boolean {
const res = getSingleChildLeaf(hostnameToTokens(suffix)); const res = this.getSingleChildLeaf(hostnameToTokens(suffix));
if (res === null) return false; if (res === null) return false;
if (!res.node[0]) return false; if (!res.node[0]) return false;
size--; this.$size--;
const { node, toPrune, tokenToPrune } = res; const { node, toPrune, tokenToPrune } = res;
if (tokenToPrune && toPrune) { if (tokenToPrune && toPrune) {
@ -352,58 +290,121 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
return true; return true;
}; };
// eslint-disable-next-line @typescript-eslint/unbound-method -- alias class methods
public delete = this.remove;
/** /**
* Method used to assert whether the given prefix exists in the Trie. * Method used to assert whether the given prefix exists in the Trie.
*/ */
const has = (suffix: string): boolean => { public has(suffix: string): boolean {
const res = walkIntoLeafWithSuffix(suffix); const res = this.walkIntoLeafWithSuffix(suffix);
return res return res
? res.node[0] ? res.node[0]
: false; : false;
}; };
function dump(onSuffix: (suffix: string) => void): void; public dump(onSuffix: (suffix: string) => void): void;
function dump(): string[]; public dump(): string[];
function dump(onSuffix?: (suffix: string) => void): string[] | void { public dump(onSuffix?: (suffix: string) => void): string[] | void {
const results: string[] = []; const results: string[] = [];
const handleSuffix = onSuffix const handleSuffix = onSuffix
? (suffix: string[]) => onSuffix(fastStringArrayJoin(suffix, '')) ? (suffix: string[]) => onSuffix(fastStringArrayJoin(suffix, ''))
: (suffix: string[]) => results.push(fastStringArrayJoin(suffix, '')); : (suffix: string[]) => results.push(fastStringArrayJoin(suffix, ''));
walk(handleSuffix); this.walk(handleSuffix);
return results; return results;
}; };
const dumpMeta = () => { public dumpMeta() {
const results: Meta[] = []; const results: Meta[] = [];
walk((suffix, meta) => { this.walk((_suffix, meta) => {
results.push(meta); results.push(meta);
}); });
return results; return results;
}; };
const dumpWithMeta = () => { public dumpWithMeta() {
const results: Array<[string, Meta]> = []; const results: Array<[string, Meta]> = [];
walk((suffix, meta) => { this.walk((suffix, meta) => {
results.push([fastStringArrayJoin(suffix, ''), meta]); results.push([fastStringArrayJoin(suffix, ''), meta]);
}); });
return results; return results;
}; };
const whitelist = (suffix: string) => { public inspect(depth: number, unpackMeta?: (meta?: Meta) => any) {
if (!smolTree) { return fastStringArrayJoin(
throw new Error('whitelist method is only available in smolTree mode.'); JSON.stringify(deepTrieNodeToJSON(this.$root, unpackMeta), null, 2).split('\n').map((line) => ' '.repeat(depth) + line),
'\n'
);
}
public [util.inspect.custom](depth: number) {
return this.inspect(depth);
};
}
export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
public smolTree = true;
add(suffix: string, meta?: Meta): void {
let node: TrieNode<Meta> = this.$root;
let curNodeChildren: Map<string, TrieNode<Meta>> = node[2];
const onToken = (token: string) => {
curNodeChildren = node[2];
if (curNodeChildren.has(token)) {
node = curNodeChildren.get(token)!;
// During the adding of `[start]blog|.skk.moe` and find out that there is a `[start].skk.moe` in the trie, skip adding the rest of the node
if (node[0] && token === '.') {
return true;
}
} else {
const newNode = createNode(node);
curNodeChildren.set(token, newNode);
node = newNode;
}
return false;
};
// When walkHostnameTokens returns true, we should skip the rest
if (walkHostnameTokens(suffix, onToken)) {
return;
} }
// If we are in smolTree mode, we need to do something at the end of the loop
if (suffix[0] === '.') {
// Trying to add `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
// Make sure parent `[start]sub.example.com` (without dot) is removed (SETINEL to false)
(/** parent */ node[1]!)[0] = false;
// Removing the rest of the parent's child nodes
node[2].clear();
// The SENTINEL of this node will be set to true at the end of the function, so we don't need to set it here
// we can use else-if here, because the children is now empty, we don't need to check the leading "."
} else if (node[2].get('.')?.[0] === true) {
// Trying to add `example.com` when there is already a `.example.com` in the trie
// No need to increment size and set SENTINEL to true (skip this "new" item)
return;
}
node[0] = true;
node[3] = meta!;
}
public whitelist(suffix: string) {
const tokens = hostnameToTokens(suffix); const tokens = hostnameToTokens(suffix);
const res = getSingleChildLeaf(tokens); const res = this.getSingleChildLeaf(tokens);
if (res === null) return; if (res === null) return;
@ -433,45 +434,48 @@ export const createTrie = <Meta = any>(from?: string[] | Set<string> | null, smo
node[0] = false; node[0] = false;
} }
}; };
}
// Actually build trie export class HostnameTrie<Meta = any> extends Triebase<Meta> {
if (Array.isArray(from)) { get size() {
for (let i = 0, l = from.length; i < l; i++) { return this.$size;
add(from[i]);
}
} else if (from) {
from.forEach((value) => add(value));
} }
const inspect = (depth: number, unpackMeta?: (meta?: Meta) => any) => fastStringArrayJoin( add(suffix: string, meta?: Meta): void {
JSON.stringify(deepTrieNodeToJSON(root, unpackMeta), null, 2).split('\n').map((line) => ' '.repeat(depth) + line), let node: TrieNode<Meta> = this.$root;
'\n'
);
return { const onToken = (token: string) => {
add, if (node[2].has(token)) {
contains, node = node[2].get(token)!;
find, } else {
remove, const newNode = createNode(node);
delete: remove, node[2].set(token, newNode);
has, node = newNode;
dump,
dumpMeta,
dumpWithMeta,
get size() {
if (smolTree) {
throw new Error('A Trie with smolTree enabled cannot have correct size!');
} }
return size;
}, return false;
get root() { };
return root;
}, // When walkHostnameTokens returns true, we should skip the rest
whitelist, if (walkHostnameTokens(suffix, onToken)) {
inspect, return;
[util.inspect.custom]: inspect, }
smolTree
}; if (!node[0]) {
this.$size++;
node[0] = true;
node[3] = meta!;
}
}
}
export function createTrie<Meta = any>(from: string[] | Set<string> | null, smolTree: true): HostnameSmolTrie<Meta>;
export function createTrie<Meta = any>(from?: string[] | Set<string> | null, smolTree?: false): HostnameTrie<Meta>;
export function createTrie<_Meta = any>(from?: string[] | Set<string> | null, smolTree = true) {
if (smolTree) {
return new HostnameSmolTrie(from);
}
return new HostnameTrie(from);
}; };
export type Trie = ReturnType<typeof createTrie>; export type Trie = ReturnType<typeof createTrie>;

View File

@ -7,7 +7,7 @@ import { parseFelixDnsmasq } from './lib/parse-dnsmasq';
import { SOURCE_DIR } from './constants/dir'; import { SOURCE_DIR } from './constants/dir';
export const parseDomesticList = async () => { export const parseDomesticList = async () => {
const trie = createTrie(await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf'), true); 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>();

View File

@ -76,7 +76,7 @@ export const parseGfwList = async () => {
})).text(); })).text();
const topDomains = parse(res); const topDomains = parse(res);
const trie = createTrie(blackSet, true); const trie = createTrie(blackSet);
for await (const [domain] of topDomains) { for await (const [domain] of topDomains) {
if (trie.has(domain)) { if (trie.has(domain)) {