domainset.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. 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. });
  19. if (!this.apexDomainMap || !this.subDomainMap) {
  20. const { domainMap, subdomainMap } = buildParseDomainMap(results);
  21. this.apexDomainMap = domainMap;
  22. this.subDomainMap = subdomainMap;
  23. }
  24. const sorted = sortDomains(results, this.apexDomainMap, this.subDomainMap);
  25. sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe');
  26. return sorted;
  27. }
  28. surge(): string[] {
  29. return this.$preprocessed;
  30. }
  31. clash(): string[] {
  32. return this.$preprocessed.map(i => (i[0] === '.' ? `+${i}` : i));
  33. }
  34. singbox(): string[] {
  35. const domains: string[] = [];
  36. const domainSuffixes: string[] = [];
  37. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  38. const domain = this.$preprocessed[i];
  39. if (domain[0] === '.') {
  40. domainSuffixes.push(domain.slice(1));
  41. } else {
  42. domains.push(domain);
  43. }
  44. }
  45. return RuleOutput.jsonToLines({
  46. version: 2,
  47. rules: [{
  48. domain: domains,
  49. domain_suffix: domainSuffixes
  50. }]
  51. } satisfies SingboxSourceFormat);
  52. }
  53. getStatMap() {
  54. invariant(this.$preprocessed, 'Non dumped yet');
  55. invariant(this.apexDomainMap, 'Missing apex domain map');
  56. return Array.from(this.$preprocessed
  57. .reduce<Map<string, number>>(
  58. (acc, cur) => {
  59. const suffix = this.apexDomainMap!.get(cur);
  60. if (suffix) {
  61. acc.set(suffix, (acc.get(suffix) ?? 0) + 1);
  62. }
  63. return acc;
  64. },
  65. new Map()
  66. )
  67. .entries())
  68. .filter(a => a[1] > 9)
  69. .sort(
  70. (a, b) => (b[1] - a[1]) || a[0].localeCompare(b[0])
  71. )
  72. .map(([domain, count]) => `${domain}${' '.repeat(100 - domain.length)}${count}`);
  73. }
  74. mitmSgmodule = undefined;
  75. adguardhome(whitelist: Set<string | `.${string}`>): string[] {
  76. const results: string[] = [];
  77. // whitelist
  78. appendArrayFromSet(results, whitelist, i => (i[0] === '.' ? '@@||' + i.slice(1) + '^' : '@@|' + i + '^'));
  79. for (let i = 0, len = this.$preprocessed.length; i < len; i++) {
  80. const domain = this.$preprocessed[i];
  81. if (domain[0] === '.') {
  82. results.push(`||${domain.slice(1)}^`);
  83. } else {
  84. results.push(`|${domain}^`);
  85. }
  86. }
  87. return results;
  88. }
  89. }