domainset.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { invariant } from 'foxact/invariant';
  2. import createKeywordFilter from '../aho-corasick';
  3. import { buildParseDomainMap, sortDomains } from '../stable-sort-domain';
  4. import { RuleOutput } from './base';
  5. import type { SingboxSourceFormat } from '../singbox';
  6. type Preprocessed = string[];
  7. export class DomainsetOutput extends RuleOutput<Preprocessed> {
  8. protected type = 'domainset' as const;
  9. preprocess() {
  10. const kwfilter = createKeywordFilter(this.domainKeywords);
  11. const results: string[] = [];
  12. this.domainTrie.dump((domain) => {
  13. if (kwfilter(domain)) {
  14. return;
  15. }
  16. results.push(domain);
  17. }, true);
  18. const sorted = results;
  19. sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe');
  20. return sorted;
  21. }
  22. surge(): string[] {
  23. return this.$preprocessed;
  24. }
  25. clash(): string[] {
  26. return this.$preprocessed.map(i => (i[0] === '.' ? `+${i}` : i));
  27. }
  28. singbox(): string[] {
  29. const domains: string[] = [];
  30. const domainSuffixes: string[] = [];
  31. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  32. const domain = this.$preprocessed[i];
  33. if (domain[0] === '.') {
  34. domainSuffixes.push(domain.slice(1));
  35. } else {
  36. domains.push(domain);
  37. }
  38. }
  39. return RuleOutput.jsonToLines({
  40. version: 2,
  41. rules: [{
  42. domain: domains,
  43. domain_suffix: domainSuffixes
  44. }]
  45. } satisfies SingboxSourceFormat);
  46. }
  47. protected apexDomainMap: Map<string, string> | null = null;
  48. protected subDomainMap: Map<string, string> | null = null;
  49. withDomainMap(apexDomainMap: Map<string, string>, subDomainMap: Map<string, string>) {
  50. this.apexDomainMap = apexDomainMap;
  51. this.subDomainMap = subDomainMap;
  52. return this;
  53. }
  54. getStatMap() {
  55. invariant(this.$preprocessed, 'Non dumped yet');
  56. if (!this.apexDomainMap || !this.subDomainMap) {
  57. const { domainMap } = buildParseDomainMap(this.$preprocessed);
  58. this.apexDomainMap = domainMap;
  59. }
  60. return Array.from(this.$preprocessed
  61. .reduce<Map<string, number>>(
  62. (acc, cur) => {
  63. const suffix = this.apexDomainMap!.get(cur);
  64. if (suffix) {
  65. acc.set(suffix, (acc.get(suffix) ?? 0) + 1);
  66. }
  67. return acc;
  68. },
  69. new Map()
  70. )
  71. .entries())
  72. .filter(a => a[1] > 9)
  73. .sort((a, b) => (b[1] - a[1]) || a[0].localeCompare(b[0]))
  74. .map(([domain, count]) => `${domain}${' '.repeat(100 - domain.length)}${count}`);
  75. }
  76. mitmSgmodule = undefined;
  77. adguardhome(whitelist: Set<string | `.${string}`>): string[] {
  78. const results: string[] = [];
  79. const whitelistArray = sortDomains(Array.from(whitelist));
  80. for (let i = 0, len = whitelistArray.length; i < len; i++) {
  81. const domain = whitelistArray[i];
  82. if (domain[0] === '.') {
  83. results.push(`@@||${domain.slice(1)}`);
  84. } else {
  85. results.push(`@@|${domain}`);
  86. }
  87. }
  88. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  89. const domain = this.$preprocessed[i];
  90. if (domain[0] === '.') {
  91. results.push(`||${domain.slice(1)}`);
  92. } else {
  93. results.push(`|${domain}`);
  94. }
  95. }
  96. for (const wildcard of this.domainWildcard) {
  97. const processed = wildcard.replaceAll('?', '*');
  98. if (processed.startsWith('*.')) {
  99. results.push(`||${processed.slice(2)}^`);
  100. } else {
  101. results.push(`|${processed}^`);
  102. }
  103. }
  104. for (const keyword of this.domainKeywords) {
  105. // Use regex to match keyword
  106. results.push(`/${keyword}/`);
  107. }
  108. for (const ipGroup of [this.ipcidr, this.ipcidrNoResolve]) {
  109. for (const ipcidr of ipGroup) {
  110. if (ipcidr.endsWith('/32')) {
  111. results.push(`||${ipcidr.slice(0, -3)}`);
  112. } else if (ipcidr.endsWith('.0/24')) {
  113. results.push(`||${ipcidr.slice(0, -6)}.*`);
  114. } else {
  115. results.push(`||${ipcidr}^`);
  116. }
  117. }
  118. }
  119. for (const ipGroup of [this.ipcidr6, this.ipcidr6NoResolve]) {
  120. for (const ipcidr of ipGroup) {
  121. if (ipcidr.endsWith('/128')) {
  122. results.push(`||${ipcidr.slice(0, -4)}`);
  123. } else {
  124. results.push(`||${ipcidr}`);
  125. }
  126. }
  127. }
  128. return results;
  129. }
  130. }