domainset.ts 2.5 KB

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