domainset.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { invariant } from 'foxact/invariant';
  2. import createKeywordFilter from '../aho-corasick';
  3. import { buildParseDomainMap } from '../stable-sort-domain';
  4. import { RuleOutput } from './base';
  5. import type { SingboxSourceFormat } from '../singbox';
  6. import { appendArrayFromSet } from '../misc';
  7. type Preprocessed = string[];
  8. export class DomainsetOutput extends RuleOutput<Preprocessed> {
  9. protected type = 'domainset' as const;
  10. preprocess() {
  11. const kwfilter = createKeywordFilter(this.domainKeywords);
  12. const results: string[] = [];
  13. this.domainTrie.dump((domain) => {
  14. if (kwfilter(domain)) {
  15. return;
  16. }
  17. results.push(domain);
  18. }, true);
  19. const sorted = results;
  20. sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe');
  21. return sorted;
  22. }
  23. surge(): string[] {
  24. return this.$preprocessed;
  25. }
  26. clash(): string[] {
  27. return this.$preprocessed.map(i => (i[0] === '.' ? `+${i}` : i));
  28. }
  29. singbox(): string[] {
  30. const domains: string[] = [];
  31. const domainSuffixes: string[] = [];
  32. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  33. const domain = this.$preprocessed[i];
  34. if (domain[0] === '.') {
  35. domainSuffixes.push(domain.slice(1));
  36. } else {
  37. domains.push(domain);
  38. }
  39. }
  40. return RuleOutput.jsonToLines({
  41. version: 2,
  42. rules: [{
  43. domain: domains,
  44. domain_suffix: domainSuffixes
  45. }]
  46. } satisfies SingboxSourceFormat);
  47. }
  48. protected apexDomainMap: Map<string, string> | null = null;
  49. protected subDomainMap: Map<string, string> | null = null;
  50. withDomainMap(apexDomainMap: Map<string, string>, subDomainMap: Map<string, string>) {
  51. this.apexDomainMap = apexDomainMap;
  52. this.subDomainMap = subDomainMap;
  53. return this;
  54. }
  55. getStatMap() {
  56. invariant(this.$preprocessed, 'Non dumped yet');
  57. if (!this.apexDomainMap || !this.subDomainMap) {
  58. const { domainMap } = buildParseDomainMap(this.$preprocessed);
  59. this.apexDomainMap = domainMap;
  60. }
  61. return Array.from(this.$preprocessed
  62. .reduce<Map<string, number>>(
  63. (acc, cur) => {
  64. const suffix = this.apexDomainMap!.get(cur);
  65. if (suffix) {
  66. acc.set(suffix, (acc.get(suffix) ?? 0) + 1);
  67. }
  68. return acc;
  69. },
  70. new Map()
  71. )
  72. .entries())
  73. .filter(a => a[1] > 9)
  74. .sort((a, b) => (b[1] - a[1]) || a[0].localeCompare(b[0]))
  75. .map(([domain, count]) => `${domain}${' '.repeat(100 - domain.length)}${count}`);
  76. }
  77. mitmSgmodule = undefined;
  78. adguardhome(whitelist: Set<string | `.${string}`>): string[] {
  79. const results: string[] = [];
  80. // whitelist
  81. appendArrayFromSet(results, whitelist, i => (i[0] === '.' ? '@@||' + i.slice(1) + '^' : '@@|' + i + '^'));
  82. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  83. const domain = this.$preprocessed[i];
  84. if (domain[0] === '.') {
  85. results.push(`||${domain.slice(1)}^`);
  86. } else {
  87. results.push(`|${domain}^`);
  88. }
  89. }
  90. return results;
  91. }
  92. }