domainset.ts 2.5 KB

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