瀏覽代碼

New build infra: Build for Clash (#11)

Sukka 2 年之前
父節點
當前提交
df4a10e180
共有 45 個文件被更改,包括 658 次插入234 次删除
  1. 2 13
      .gitignore
  2. 31 19
      Build/build-anti-bogus-domain.js
  3. 39 24
      Build/build-apple-cdn.js
  4. 16 35
      Build/build-cdn-conf.js
  5. 31 14
      Build/build-chn-cidr.js
  6. 20 8
      Build/build-domestic-ruleset.js
  7. 30 16
      Build/build-phishing-domainset.js
  8. 26 13
      Build/build-reject-domainset.js
  9. 26 13
      Build/build-speedtest-domainset.js
  10. 28 14
      Build/build-telegram-cidr.js
  11. 179 0
      Build/build.js
  12. 16 11
      Build/download-previous-build.js
  13. 34 0
      Build/lib/clash.js
  14. 0 6
      List/domainset/icloud_private_relay.conf
  15. 0 1
      List/domainset/my_proxy.conf
  16. 0 2
      List/ip/domestic.conf
  17. 0 1
      List/non_ip/speedtest.conf
  18. 55 29
      README.md
  19. 3 0
      Source/domainset/cdn.conf
  20. 3 0
      Source/domainset/download.conf
  21. 9 0
      Source/domainset/icloud_private_relay.conf
  22. 2 0
      Source/domainset/reject_sukka.conf
  23. 3 0
      Source/ip/apple_services.conf
  24. 4 0
      Source/ip/domestic.conf
  25. 3 0
      Source/ip/download.conf
  26. 2 1
      Source/ip/neteasemusic.conf
  27. 2 0
      Source/ip/reject.conf
  28. 2 1
      Source/ip/stream.conf
  29. 3 0
      Source/non_ip/apple_services.conf
  30. 2 0
      Source/non_ip/cdn.conf
  31. 3 0
      Source/non_ip/direct.conf
  32. 2 0
      Source/non_ip/domestic.conf
  33. 3 2
      Source/non_ip/global.conf
  34. 2 1
      Source/non_ip/global_plus.conf
  35. 2 0
      Source/non_ip/my_direct.conf
  36. 2 0
      Source/non_ip/my_proxy.conf
  37. 2 0
      Source/non_ip/my_reject.conf
  38. 2 1
      Source/non_ip/neteasemusic.conf
  39. 3 0
      Source/non_ip/reject-no-drop.conf
  40. 3 0
      Source/non_ip/reject.conf
  41. 3 1
      Source/non_ip/sogouinput.conf
  42. 2 1
      Source/non_ip/stream.conf
  43. 3 1
      Source/non_ip/telegram.conf
  44. 12 0
      package.json
  45. 43 6
      pnpm-lock.yaml

+ 2 - 13
.gitignore

@@ -4,18 +4,7 @@ node_modules
 .wireit
 public
 
-List/domainset/reject.conf
-List/domainset/cdn.conf
-List/domainset/reject_phishing.conf
-List/domainset/reject_sukka.conf
-List/domainset/apple_cdn.conf
-List/domainset/speedtest.conf
-List/non_ip/cdn.conf
-List/non_ip/domestic.conf
-List/non_ip/apple_cdn.conf
-List/ip/telegram.conf
-List/ip/reject.conf
-List/ip/china_ip.conf
-List/internal/
+List
+Clash
 
 Modules/sukka_local_dns_mapping.sgmodule

+ 31 - 19
Build/build-anti-bogus-domain.js

@@ -4,7 +4,7 @@ const { isIPv4, isIPv6 } = require('net');
 const { compareAndWriteFile } = require('./lib/string-array-compare');
 const { withBannerArray } = require('./lib/with-banner');
 const { fetchRemoteTextAndCreateReadlineInterface, readFileByLine } = require('./lib/fetch-remote-text-by-line');
-const { minifyRules } = require('./lib/minify-rules');
+const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
 
 (async () => {
   console.time('Total Time - build-anti-bogus-domain');
@@ -21,7 +21,6 @@ const { minifyRules } = require('./lib/minify-rules');
   console.timeEnd('* Download bogus-nxdomain-list');
 
   const filePath = path.resolve(__dirname, '../Source/ip/reject.conf');
-  const resultPath = path.resolve(__dirname, '../List/ip/reject.conf');
 
   /** @type {string[]} */
   const result = [];
@@ -39,24 +38,37 @@ const { minifyRules } = require('./lib/minify-rules');
     }
   }
 
-  await compareAndWriteFile(
-    withBannerArray(
-      'Sukka\'s Surge Rules - Anti Bogus Domain',
-      [
-        'License: AGPL 3.0',
-        'Homepage: https://ruleset.skk.moe',
-        'GitHub: https://github.com/SukkaW/Surge',
-        '',
-        'This file contains known addresses that are hijacking NXDOMAIN results returned by DNS servers.',
-        '',
-        'Data from:',
-        ' - https://github.com/felixonmars/dnsmasq-china-list'
-      ],
-      new Date(),
-      minifyRules(result)
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'This file contains known addresses that are hijacking NXDOMAIN results returned by DNS servers.',
+    '',
+    'Data from:',
+    ' - https://github.com/felixonmars/dnsmasq-china-list'
+  ];
+
+  await Promise.all([
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Anti Bogus Domain',
+        description,
+        new Date(),
+        result
+      ),
+      path.resolve(__dirname, '../List/ip/reject.conf')
     ),
-    resultPath
-  );
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Anti Bogus Domain',
+        description,
+        new Date(),
+        surgeRulesetToClashClassicalTextRuleset(result)
+      ),
+      path.resolve(__dirname, '../Clash/ip/reject.txt')
+    )
+  ]);
 
   console.timeEnd('Total Time - build-anti-bogus-domain');
 })();

+ 39 - 24
Build/build-apple-cdn.js

@@ -4,48 +4,63 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
 const { withBannerArray } = require('./lib/with-banner');
 
 const { parseFelixDnsmasq } = require('./lib/parse-dnsmasq');
+const { surgeRulesetToClashClassicalTextRuleset, surgeDomainsetToClashDomainset } = require('./lib/clash');
 
 (async () => {
   console.time('Total Time - build-apple-cdn-conf');
 
   const res = await parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/apple.china.conf');
 
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'This file contains Apple\'s domains using their China mainland CDN servers.',
+    '',
+    'Data from:',
+    ' - https://github.com/felixonmars/dnsmasq-china-list'
+  ];
+
+  const ruleset = res.map(domain => `DOMAIN-SUFFIX,${domain}`);
+  const domainset = res.map(i => `.${i}`);
+
   await Promise.all([
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - Apple CDN',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'This file contains Apple\'s domains using their China mainland CDN servers.',
-          '',
-          'Data from:',
-          ' - https://github.com/felixonmars/dnsmasq-china-list'
-        ],
+        'Sukka\'s Ruleset - Apple CDN',
+        description,
         new Date(),
-        res.map(domain => `DOMAIN-SUFFIX,${domain}`)
+        ruleset
       ),
       path.resolve(__dirname, '../List/non_ip/apple_cdn.conf')
     ),
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - Apple CDN',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'This file contains Apple\'s domains using their China mainland CDN servers.',
-          '',
-          'Data from:',
-          ' - https://github.com/felixonmars/dnsmasq-china-list'
-        ],
+        'Sukka\'s Ruleset - Apple CDN',
+        description,
+        new Date(),
+        surgeRulesetToClashClassicalTextRuleset(ruleset)
+      ),
+      path.resolve(__dirname, '../Clash/non_ip/apple_cdn.txt')
+    ),
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Apple CDN',
+        description,
         new Date(),
-        res.map(i => `.${i}`)
+        domainset
       ),
       path.resolve(__dirname, '../List/domainset/apple_cdn.conf')
+    ),
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Apple CDN',
+        description,
+        new Date(),
+        surgeDomainsetToClashDomainset(domainset)
+      ),
+      path.resolve(__dirname, '../Clash/domainset/apple_cdn.txt')
     )
   ]);
 

+ 16 - 35
Build/build-cdn-conf.js

@@ -3,10 +3,9 @@ const path = require('path');
 const { compareAndWriteFile } = require('./lib/string-array-compare');
 const { withBannerArray } = require('./lib/with-banner');
 const { minifyRules } = require('./lib/minify-rules');
-const { domainDeduper } = require('./lib/domain-deduper');
-const { processLine } = require('./lib/process-line');
 const { fetchRemoteTextAndCreateReadlineInterface, readFileByLine } = require('./lib/fetch-remote-text-by-line');
 const Trie = require('./lib/trie');
+const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
 
 (async () => {
   console.time('Total Time - build-cdn-conf');
@@ -40,51 +39,33 @@ const Trie = require('./lib/trie');
     }
   }
 
-  /**
-   * Dedupe cdn.conf
-   */
-  /** @type {Set<string>} */
-  const cdnDomains = new Set();
-
-  for await (const line of readFileByLine(
-    path.resolve(__dirname, '../Source/domainset/cdn.conf')
-  )) {
-    const l = processLine(line);
-    if (l) {
-      cdnDomains.add(l);
-    }
-  }
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'This file contains object storage and static assets CDN domains.'
+  ];
+  const ruleset = minifyRules(cdnDomainsList);
 
   await Promise.all([
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - CDN Domains',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'This file contains object storage and static assets CDN domains.'
-        ],
+        'Sukka\'s Ruleset - CDN Domains',
+        description,
         new Date(),
-        minifyRules(cdnDomainsList)
+        ruleset
       ),
       path.resolve(__dirname, '../List/non_ip/cdn.conf')
     ),
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - CDN Domains',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'This file contains object storage and static assets CDN domains.'
-        ],
+        'Sukka\'s Ruleset - CDN Domains',
+        description,
         new Date(),
-        minifyRules(domainDeduper(Array.from(cdnDomains)))
+        surgeRulesetToClashClassicalTextRuleset(ruleset)
       ),
-      path.resolve(__dirname, '../List/domainset/cdn.conf')
+      path.resolve(__dirname, '../Clash/non_ip/cdn.txt')
     )
   ]);
 

+ 31 - 14
Build/build-chn-cidr.js

@@ -28,21 +28,38 @@ const EXCLUDE_CIDRS = [
   const filteredCidr = excludeCidrs(Array.from(cidr), EXCLUDE_CIDRS, true);
   console.log('After Merge:', filteredCidr.length);
 
-  await compareAndWriteFile(
-    withBannerArray(
-      'Sukka\'s Surge Rules - Mainland China IPv4 CIDR',
-      [
-        'License: CC BY-SA 2.0',
-        'Homepage: https://ruleset.skk.moe',
-        'GitHub: https://github.com/SukkaW/Surge',
-        '',
-        'Data from https://misaka.io (misakaio @ GitHub)'
-      ],
-      new Date(),
-      filteredCidr.map(i => `IP-CIDR,${i}`)
+  await Promise.all([
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
+        [
+          'License: CC BY-SA 2.0',
+          'Homepage: https://ruleset.skk.moe',
+          'GitHub: https://github.com/SukkaW/Surge',
+          '',
+          'Data from https://misaka.io (misakaio @ GitHub)'
+        ],
+        new Date(),
+        filteredCidr.map(i => `IP-CIDR,${i}`)
+      ),
+      pathResolve(__dirname, '../List/ip/china_ip.conf')
     ),
-    pathResolve(__dirname, '../List/ip/china_ip.conf')
-  );
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Mainland China IPv4 CIDR',
+        [
+          'License: CC BY-SA 2.0',
+          'Homepage: https://ruleset.skk.moe',
+          'GitHub: https://github.com/SukkaW/Surge',
+          '',
+          'Data from https://misaka.io (misakaio @ GitHub)'
+        ],
+        new Date(),
+        filteredCidr
+      ),
+      pathResolve(__dirname, '../Clash/ip/china_ip.txt')
+    )
+  ]);
 
   console.timeEnd('Total Time - build-chnroutes-cidr');
 })();

+ 20 - 8
Build/build-domestic-ruleset.js

@@ -6,6 +6,7 @@ const { processLine } = require('./lib/process-line');
 const { withBannerArray } = require('./lib/with-banner');
 const { compareAndWriteFile } = require('./lib/string-array-compare');
 const domainSorter = require('./lib/stable-sort-domain');
+const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
 
 (async () => {
   const rl = readFileByLine(path.resolve(__dirname, '../Source/non_ip/domestic.conf'));
@@ -25,22 +26,33 @@ const domainSorter = require('./lib/stable-sort-domain');
       .map((domain) => `DOMAIN-SUFFIX,${domain}`)
   );
 
+  const rulesetDescription = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'This file contains known addresses that are avaliable in the Mainland China.'
+  ];
+
   await Promise.all([
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - Domestic Domain',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'This file contains known addresses that are avaliable in the Mainland China.'
-        ],
+        'Sukka\'s Ruleset - Domestic Domains',
+        rulesetDescription,
         new Date(),
         results
       ),
       path.resolve(__dirname, '../List/non_ip/domestic.conf')
     ),
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Domestic Domains',
+        rulesetDescription,
+        new Date(),
+        surgeRulesetToClashClassicalTextRuleset(results)
+      ),
+      path.resolve(__dirname, '../Clash/non_ip/domestic.txt')
+    ),
     compareAndWriteFile(
       [
         '#!name=[Sukka] Local DNS Mapping',

+ 30 - 16
Build/build-phishing-domainset.js

@@ -5,6 +5,7 @@ const { withBannerArray } = require('./lib/with-banner.js');
 const { compareAndWriteFile } = require('./lib/string-array-compare');
 const { processLine } = require('./lib/process-line.js');
 const domainSorter = require('./lib/stable-sort-domain');
+const { surgeDomainsetToClashDomainset } = require('./lib/clash.js');
 
 const WHITELIST_DOMAIN = new Set([
   'w3s.link',
@@ -141,21 +142,34 @@ const BLACK_TLD = new Set([
 
   results.sort(domainSorter);
 
-  await compareAndWriteFile(
-    withBannerArray(
-      'Sukka\'s Surge Rules - Reject Phishing',
-      [
-        'License: AGPL 3.0',
-        'Homepage: https://ruleset.skk.moe',
-        'GitHub: https://github.com/SukkaW/Surge',
-        '',
-        'The domainset supports enhanced phishing protection',
-        'Build from:',
-        ' - https://gitlab.com/malware-filter/phishing-filter'
-      ],
-      new Date(),
-      results
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'The domainset supports enhanced phishing protection',
+    'Build from:',
+    ' - https://gitlab.com/malware-filter/phishing-filter'
+  ];
+
+  await Promise.all([
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Reject Phishing',
+        description,
+        new Date(),
+        results
+      ),
+      path.resolve(__dirname, '../List/domainset/reject_phishing.conf')
     ),
-    path.resolve(__dirname, '../List/domainset/reject_phishing.conf')
-  );
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Reject Phishing',
+        description,
+        new Date(),
+        surgeDomainsetToClashDomainset(results)
+      ),
+      path.resolve(__dirname, '../Clash/domainset/reject_phishing.txt')
+    )
+  ]);
 })();

+ 26 - 13
Build/build-reject-domainset.js

@@ -16,6 +16,7 @@ const { domainDeduper } = require('./lib/domain-deduper');
 const createKeywordFilter = require('./lib/aho-corasick');
 const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
 const domainSorter = require('./lib/stable-sort-domain');
+const { surgeDomainsetToClashDomainset } = require('./lib/clash');
 
 /** Whitelists */
 const filterRuleWhitelistDomainSets = new Set(PREDEFINED_WHITELIST);
@@ -196,26 +197,38 @@ const domainSuffixSet = new Set();
     return acc;
   }, {});
 
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    '',
+    'The domainset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining',
+    '',
+    'Build from:',
+    ...HOSTS.map(host => ` - ${host[0]}`),
+    ...ADGUARD_FILTERS.map(filter => ` - ${Array.isArray(filter) ? filter[0] : filter}`)
+  ];
+  const domainset = dudupedDominArray.sort(domainSorter);
+
   await Promise.all([
     compareAndWriteFile(
       withBannerArray(
-        'Sukka\'s Surge Rules - Reject Base',
-        [
-          'License: AGPL 3.0',
-          'Homepage: https://ruleset.skk.moe',
-          'GitHub: https://github.com/SukkaW/Surge',
-          '',
-          'The domainset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining',
-          '',
-          'Build from:',
-          ...HOSTS.map(host => ` - ${host[0]}`),
-          ...ADGUARD_FILTERS.map(filter => ` - ${Array.isArray(filter) ? filter[0] : filter}`)
-        ],
+        'Sukka\'s Ruleset - Reject Base',
+        description,
         new Date(),
-        dudupedDominArray.sort(domainSorter)
+        domainset
       ),
       pathResolve(__dirname, '../List/domainset/reject.conf')
     ),
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Reject Base',
+        description,
+        new Date(),
+        surgeDomainsetToClashDomainset(domainset)
+      ),
+      pathResolve(__dirname, '../Clash/domainset/reject.txt')
+    ),
     fs.promises.writeFile(
       pathResolve(__dirname, '../List/internal/reject-stats.txt'),
       Object.entries(rejectDomainsStats)

+ 26 - 13
Build/build-speedtest-domainset.js

@@ -6,6 +6,7 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
 const domainSorter = require('./lib/stable-sort-domain');
 
 const { Sema } = require('async-sema');
+const { surgeDomainsetToClashDomainset } = require('./lib/clash');
 const s = new Sema(2);
 
 /**
@@ -107,19 +108,31 @@ const querySpeedtestApi = async (keyword) => {
     }
   }
 
-  const reduped = domainDeduper(Array.from(domains)).sort(domainSorter);
+  const deduped = domainDeduper(Array.from(domains)).sort(domainSorter);
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge'
+  ];
 
-  await compareAndWriteFile(
-    withBannerArray(
-      'Sukka\'s Surge Rules - Speedtest Domains',
-      [
-        'License: AGPL 3.0',
-        'Homepage: https://ruleset.skk.moe',
-        'GitHub: https://github.com/SukkaW/Surge'
-      ],
-      new Date(),
-      reduped
+  await Promise.all([
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Speedtest Domains',
+        description,
+        new Date(),
+        deduped
+      ),
+      path.resolve(__dirname, '../List/domainset/speedtest.conf')
     ),
-    path.resolve(__dirname, '../List/domainset/speedtest.conf')
-  );
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Speedtest Domains',
+        description,
+        new Date(),
+        surgeDomainsetToClashDomainset(deduped)
+      ),
+      path.resolve(__dirname, '../Clash/domainset/speedtest.txt')
+    )
+  ]);
 })();

+ 28 - 14
Build/build-telegram-cidr.js

@@ -5,6 +5,7 @@ const { isIPv4, isIPv6 } = require('net');
 const { withBannerArray } = require('./lib/with-banner');
 const { processLine } = require('./lib/process-line');
 const { compareAndWriteFile } = require('./lib/string-array-compare');
+const { surgeRulesetToClashClassicalTextRuleset } = require('./lib/clash');
 
 (async () => {
   console.time('Total Time - build-telegram-cidr');
@@ -34,21 +35,34 @@ const { compareAndWriteFile } = require('./lib/string-array-compare');
     throw new Error('Failed to fetch data!');
   }
 
-  await compareAndWriteFile(
-    withBannerArray(
-      'Sukka\'s Surge Rules - Telegram IP CIDR',
-      [
-        'License: AGPL 3.0',
-        'Homepage: https://ruleset.skk.moe',
-        'GitHub: https://github.com/SukkaW/Surge',
-        'Data from:',
-        ' - https://core.telegram.org/resources/cidr.txt'
-      ],
-      date,
-      results
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    'Data from:',
+    ' - https://core.telegram.org/resources/cidr.txt'
+  ];
+
+  await Promise.all([
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Telegram IP CIDR',
+        description,
+        date,
+        results
+      ),
+      path.resolve(__dirname, '../List/ip/telegram.conf')
     ),
-    path.resolve(__dirname, '../List/ip/telegram.conf')
-  );
+    compareAndWriteFile(
+      withBannerArray(
+        'Sukka\'s Ruleset - Telegram IP CIDR',
+        description,
+        date,
+        surgeRulesetToClashClassicalTextRuleset(results)
+      ),
+      path.resolve(__dirname, '../Clash/ip/telegram.txt')
+    )
+  ]);
 
   console.timeEnd('Total Time - build-telegram-cidr');
 })();

+ 179 - 0
Build/build.js

@@ -0,0 +1,179 @@
+// @ts-check
+
+const path = require('path');
+const { PathScurry } = require('path-scurry');
+const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
+const { processLine } = require('./lib/process-line');
+const { compareAndWriteFile } = require('./lib/string-array-compare');
+const { withBannerArray } = require('./lib/with-banner');
+const { domainDeduper } = require('./lib/domain-deduper');
+const { surgeRulesetToClashClassicalTextRuleset, surgeDomainsetToClashDomainset } = require('./lib/clash');
+
+const MAGIC_COMMAND_SKIP = '# $ custom_build_script';
+const MAGIC_COMMAND_TITLE = '# $ meta_title ';
+const MAGIC_COMMAND_DESCRIPTION = '# $ meta_description ';
+
+const sourceDir = path.resolve(__dirname, '../Source');
+const outputSurgeDir = path.resolve(__dirname, '../List');
+const outputClashDir = path.resolve(__dirname, '../Clash');
+
+(async () => {
+  /** @type {Promise<void>[]} */
+  const promises = [];
+
+  const pw = new PathScurry(sourceDir);
+  for await (const entry of pw) {
+    if (entry.isFile()) {
+      if (path.extname(entry.name) === '.js') {
+        continue;
+      }
+
+      const relativePath = entry.relative();
+      if (relativePath.startsWith('domainset/')) {
+        promises.push(transformDomainset(entry.fullpath(), relativePath));
+        continue;
+      }
+      if (
+        relativePath.startsWith('ip/')
+        || relativePath.startsWith('non_ip/')
+      ) {
+        promises.push(transformRuleset(entry.fullpath(), relativePath));
+        continue;
+      }
+    }
+  }
+
+  await Promise.all(promises);
+})();
+
+/**
+ * @param {string} sourcePath
+ */
+const processFile = async (sourcePath) => {
+  /** @type {Set<string>} */
+  const lines = new Set();
+
+  let title = '';
+  /** @type {string[]} */
+  const descriptions = [];
+
+  for await (const line of readFileByLine(sourcePath)) {
+    if (line === MAGIC_COMMAND_SKIP) {
+      return;
+    }
+
+    if (line.startsWith(MAGIC_COMMAND_TITLE)) {
+      title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
+      continue;
+    }
+
+    if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
+      descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
+      continue;
+    }
+
+    const l = processLine(line);
+    if (l) {
+      lines.add(l);
+    }
+  }
+
+  return /** @type {const} */ ([title, descriptions, lines]);
+};
+
+/**
+ * @param {string} sourcePath
+ * @param {string} relativePath
+ */
+async function transformDomainset(sourcePath, relativePath) {
+  const res = await processFile(sourcePath);
+  if (!res) return;
+  const [title, descriptions, lines] = res;
+
+  const deduped = domainDeduper(Array.from(lines));
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    ...(
+      descriptions.length
+        ? ['', ...descriptions]
+        : []
+    )
+  ];
+
+  await Promise.all([
+    // Surge DOMAIN-SET
+    compareAndWriteFile(
+      withBannerArray(
+        title,
+        description,
+        new Date(),
+        deduped
+      ),
+      path.resolve(outputSurgeDir, relativePath)
+    ),
+    // Clash domain text
+    compareAndWriteFile(
+      withBannerArray(
+        title,
+        description,
+        new Date(),
+        surgeDomainsetToClashDomainset(deduped)
+      ),
+      // change path extname to .txt
+      path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
+    )
+  ]);
+}
+
+/**
+ * Output Surge RULE-SET and Clash classical text format
+ *
+ * @param {string} sourcePath
+ * @param {string} relativePath
+ */
+async function transformRuleset(sourcePath, relativePath) {
+  const res = await processFile(sourcePath);
+  if (!res) return;
+  const [title, descriptions, set] = res;
+
+  const description = [
+    'License: AGPL 3.0',
+    'Homepage: https://ruleset.skk.moe',
+    'GitHub: https://github.com/SukkaW/Surge',
+    ...(
+      descriptions.length
+        ? ['', ...descriptions]
+        : []
+    )
+  ];
+
+  const lines = Array.from(set);
+
+  const clashSupported = surgeRulesetToClashClassicalTextRuleset(set);
+
+  await Promise.all([
+    // Surge RULE-SET
+    compareAndWriteFile(
+      withBannerArray(
+        title,
+        description,
+        new Date(),
+        lines
+      ),
+      path.resolve(outputSurgeDir, relativePath)
+    ),
+    // Clash domainset
+    compareAndWriteFile(
+      withBannerArray(
+        title,
+        description,
+        new Date(),
+        clashSupported
+      ),
+      // change path extname to .txt
+      path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
+    )
+  ]);
+}

+ 16 - 11
Build/download-previous-build.js

@@ -7,6 +7,7 @@ const { tmpdir } = require('os');
 const { Readable } = require('stream');
 const { pipeline } = require('stream/promises');
 const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
+const { isCI } = require('ci-info');
 
 const fileExists = (path) => {
   return fs.promises.access(path, fs.constants.F_OK)
@@ -19,18 +20,22 @@ const fileExists = (path) => {
 
   let allFileExists = true;
 
-  for await (const line of readFileByLine(resolve(__dirname, '../.gitignore'))) {
-    if (
-      (
-        line.startsWith('List/')
-        || line.startsWith('Modules/')
-      ) && !line.endsWith('/')
-    ) {
-      allFileExists = await fileExists(join(__dirname, '..', line));
-      filesList.push(line);
+  if (isCI) {
+    allFileExists = false;
+  } else {
+    for await (const line of readFileByLine(resolve(__dirname, '../.gitignore'))) {
+      if (
+        (
+        // line.startsWith('List/')
+          line.startsWith('Modules/')
+        ) && !line.endsWith('/')
+      ) {
+        allFileExists = await fileExists(join(__dirname, '..', line));
+        filesList.push(line);
 
-      if (!allFileExists) {
-        console.log(`File not exists: ${line}`);
+        if (!allFileExists) {
+          console.log(`File not exists: ${line}`);
+        }
       }
     }
   }

+ 34 - 0
Build/lib/clash.js

@@ -0,0 +1,34 @@
+// @ts-check
+const _Trie = require('mnemonist/trie');
+const Trie = _Trie.default || _Trie;
+
+const CLASH_SUPPORTED_RULE_TYPE = [
+  'DOMAIN-SUFFIX',
+  'DOMAIN-KEYWORD',
+  'DOMAIN',
+  'SRC-IP-CIDR',
+  'GEOIP',
+  'IP-CIDR',
+  'IP-CIDR6',
+  'DST-PORT',
+  'SRC-PORT'
+];
+
+/**
+ * @param {string[] | Set<string>} rules
+ */
+const surgeRulesetToClashClassicalTextRuleset = (rules) => {
+  const trie = Trie.from(rules);
+  return CLASH_SUPPORTED_RULE_TYPE.map(
+    type => trie.find(`${type},`)
+  ).flat();
+};
+module.exports.surgeRulesetToClashClassicalTextRuleset = surgeRulesetToClashClassicalTextRuleset;
+
+/**
+ * @param {string[]} domainset
+ */
+const surgeDomainsetToClashDomainset = (domainset) => {
+  return domainset.map(i => (i[0] === '.' ? `+${i}` : i));
+};
+module.exports.surgeDomainsetToClashDomainset = surgeDomainsetToClashDomainset;

+ 0 - 6
List/domainset/icloud_private_relay.conf

@@ -1,6 +0,0 @@
-mask.icloud.com
-mask-h2.icloud.com
-mask-api.icloud.com
-mask.apple-dns.net
-mask-api.fe.apple-dns.net
-mask-t.apple-dns.net

+ 0 - 1
List/domainset/my_proxy.conf

@@ -1 +0,0 @@
-# deprecated

+ 0 - 2
List/ip/domestic.conf

@@ -1,2 +0,0 @@
-# >> Tencent AIA
-IP-CIDR,162.14.0.0/18,no-resolve

+ 0 - 1
List/non_ip/speedtest.conf

@@ -1 +0,0 @@
-# Deprecated

+ 55 - 29
README.md

@@ -1,6 +1,6 @@
-# Surge
+# Sukka Ruleset
 
-由 [Sukka](https://skk.moe) 搜集、整理、维护的、个人自用的、适用于 [Surge](https://nssurge.com/) 的 Rule Snippet。
+由 [Sukka](https://skk.moe) 搜集、整理、维护的、个人自用的、适用于 [Surge](https://nssurge.com/) 和 [Clash Premium](https://dreamacro.github.io/clash/) 的 Rule Snippet。
 
 ## 条款和协议
 
@@ -18,6 +18,13 @@
 
 #### 广告拦截 / 隐私保护 / Malware 拦截 / Phiishing 拦截
 
+- 自动生成
+- 数据来源、白名单域名列表和生成方式,请参考 [`build-reject-domainset.js`](Build/build-reject-domainset.js)
+- 仅建议在 Surge for Mac 上使用,移动平台请使用专门的工具(如 ADGuard for Android/iOS)以获得更好的性能
+- 不能替代浏览器广告屏蔽扩展(如 uBlock Origin)
+
+**Surge**
+
 ```ini
 RULE-SET,https://ruleset.skk.moe/List/non_ip/reject.conf,REJECT
 DOMAIN-SET,https://ruleset.skk.moe/List/domainset/reject.conf,REJECT-TINYGIF
@@ -25,80 +32,97 @@ DOMAIN-SET,https://ruleset.skk.moe/List/domainset/reject_phishing.conf,REJECT
 RULE-SET,https://ruleset.skk.moe/List/ip/reject.conf,REJECT-DROP
 ```
 
-- 自动生成
-- 数据来源、白名单域名列表和生成方式,请参考 [`build-reject-domainset.js`](Build/build-reject-domainset.js)
-- 仅建议在 Surge for Mac 上使用,移动平台请使用专门的工具(如 ADGuard for Android/iOS)以获得更好的性能
-- 不能替代浏览器广告屏蔽扩展(如 uBlock Origin)
+**Clash**
 
-#### 搜狗输入法
 
-```ini
-RULE-SET,https://ruleset.skk.moe/List/non_ip/sogouinput.conf,
-```
+
+#### 搜狗输入法
 
 - 人工维护
 - 该规则组用于避免搜狗输入法将你输入的每一个字符自动收集并通过 `get.sogou.com/q` 等域名回传
 - 影响搜狗输入法账号同步、词库更新、问题反馈
 
-#### 常见静态 CDN
+**Surge**
 
 ```ini
-DOMAIN-SET,https://ruleset.skk.moe/List/domainset/cdn.conf,[Replace with your policy]
-RULE-SET,https://ruleset.skk.moe/List/non_ip/cdn.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/non_ip/sogouinput.conf,
 ```
 
+#### 常见静态 CDN
+
 - 自动生成 + 人工维护
 - 包含所有常见静态资源 CDN 域名、对象存储域名
 - 如果你正在使用商业性质的公共代理服务、且你的服务商提供按低倍率结算流量消耗的节点,可使用上述规则组将流量分配给这部分节点
 
-#### 流媒体
+**Surge**
 
 ```ini
-RULE-SET,https://ruleset.skk.moe/List/non_ip/stream.conf,[Replace with your policy]
-RULE-SET,https://ruleset.skk.moe/List/ip/stream.conf,[Replace with your policy]
+DOMAIN-SET,https://ruleset.skk.moe/List/domainset/cdn.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/non_ip/cdn.conf,[Replace with your policy]
 ```
 
+#### 流媒体
+
 - 人工维护
 - 包含 4gtv、AbemaTV、All4、Amazon Prime Video、Apple TV、Apple Music TV、Bahamut、BBC、Bilibili Intl、DAZN、Deezer、Disney+、Discovery+、DMM、encoreTVB、Fox Now、Fox+、HBO GO/Now/Max/Asia、Hulu、HWTV、JOOX、Jwplayer、KKBOX、KKTV、Line TV、Naver TV、myTV Super、Netflix、niconico、Now E、Paramount+、PBS、Peacock、Pandora、PBS、Pornhub、SoundCloud、PBS、Spotify、TaiwanGood、Tiktok Intl、Twitch、ViuTV、ShowTime、iQiYi Global、Himalaya Podcast、Overcast、WeTV 的规则组
 
-#### Telegram
+**Surge**
 
 ```ini
-RULE-SET,https://ruleset.skk.moe/List/non_ip/telegram.conf,[Replace with your policy]
-RULE-SET,https://ruleset.skk.moe/List/ip/telegram.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/non_ip/stream.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/ip/stream.conf,[Replace with your policy]
 ```
 
+#### Telegram
+
 - 域名规则 人工维护
 - IP CIDR 规则 自动生成(数据来源:[`https://core.telegram.org/resources/cidr.txt`](https://core.telegram.org/resources/cidr.txt))
 
-#### Apple CDN
+**Surge**
 
 ```ini
-DOMAIN-SET,https://ruleset.skk.moe/List/domainset/apple_cdn.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/non_ip/telegram.conf,[Replace with your policy]
+RULE-SET,https://ruleset.skk.moe/List/ip/telegram.conf,[Replace with your policy]
 ```
 
+#### Apple CDN
+
 - 自动生成
 - 规则组包含 Apple, Inc. 在中华人民共和国完成工信部 ICP 备案和公安网备、且在中华人民共和国境内提供 HTTP 服务的域名,如果由于某些原因需要代理其中部分域名,请自行针对域名编写规则、并添加到当前规则组之前。
 - 数据来源 [`felixonmars/dnsmasq-china-list`](https://github.com/felixonmars/dnsmasq-china-list/blob/master/apple.china.conf)
 
+**Surge**
+
+```ini
+DOMAIN-SET,https://ruleset.skk.moe/List/domainset/apple_cdn.conf,[Replace with your policy]
+```
+
 #### Apple Service
 
+- 人工维护
+
+**Surge**
+
 ```ini
 RULE-SET,https://ruleset.skk.moe/List/non_ip/apple_services.conf,[Replace with your policy]
 ```
 
+#### 网易云音乐
+
 - 人工维护
 
-#### 网易云音乐
+**Surge**
 
 ```ini
 RULE-SET,https://ruleset.skk.moe/List/non_ip/neteasemusic.conf,[Replace with your policy]
 RULE-SET,https://ruleset.skk.moe/List/ip/neteasemusic.conf,[Replace with your policy]
 ```
 
+#### Misc
+
 - 人工维护
 
-#### Misc
+**Surge**
 
 ```ini
 RULE-SET,https://ruleset.skk.moe/List/non_ip/domestic.conf,[Replace with your policy]
@@ -108,17 +132,17 @@ RULE-SET,https://ruleset.skk.moe/List/non_ip/global.conf,PROXY
 RULE-SET,https://ruleset.skk.moe/List/ip/domestic.conf,[Replace with your policy]
 ```
 
-- 人工维护
-
 #### chnroute CIDR
 
+- 自动生成
+- [原始数据](https://github.com/misakaio/chnroutes2) 由 Misaka Network, Inc.、DMIT, Inc.、NEROCLOUD Ltd.、Rainbow network Ltd.、MOACK Co., Ltd. 提供,由 Misaka Network, Inc. 整理,以 [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/) 协议发布
+
+**Surge**
+
 ```ini
 RULE-SET,https://ruleset.skk.moe/List/ip/china_ip.conf,[Replace with your policy]
 ```
 
-- 自动生成
-- [原始数据](https://github.com/misakaio/chnroutes2) 由 Misaka Network, Inc.、DMIT, Inc.、NEROCLOUD Ltd.、Rainbow network Ltd.、MOACK Co., Ltd. 提供,由 Misaka Network, Inc. 整理,以 [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/) 协议发布
-
 ## Surge 模块列表
 
 - Sukka URL Rewrite: `https://ruleset.skk.moe/Modules/sukka_url_rewrite.sgmodule`
@@ -139,7 +163,9 @@ RULE-SET,https://ruleset.skk.moe/List/ip/china_ip.conf,[Replace with your policy
 
 **有适用于 Clash 的规则组吗?**
 
-没有。如果 [Clash Premium 提供了对 `DOMAIN-SET` 格式的支持](https://github.com/Dreamacro/clash/issues/1838),未来可能会有。
+~~没有。如果 [Clash Premium 提供了对 `DOMAIN-SET` 格式的支持](https://github.com/Dreamacro/clash/issues/1838),未来可能会有。~~
+
+有。
 
 **有适用于 Shadowrocket、Quantumult X、Loon、V2RayNG 的规则组吗?**
 

+ 3 - 0
Source/domainset/cdn.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - CDN Domains
+# $ meta_description This file contains object storage and static assets CDN domains.
+
 # >> Sukka
 ruleset.skk.moe
 pic.skk.moe

+ 3 - 0
List/domainset/download.conf → Source/domainset/download.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Large Files Hosting Domains
+# $ meta_description This file contains domains for software updating & large file hosting.
+
 .1fichier.com
 .1fichier.info
 .nitro.download

+ 9 - 0
Source/domainset/icloud_private_relay.conf

@@ -0,0 +1,9 @@
+# $ meta_title Sukka's Ruleset - iCloud Private Relay
+# $ meta_description This file contains domains for iCloud Private Relay Endpoint.
+
+mask.icloud.com
+mask-h2.icloud.com
+mask-api.icloud.com
+mask.apple-dns.net
+mask-api.fe.apple-dns.net
+mask-t.apple-dns.net

+ 2 - 0
Source/domainset/reject_sukka.conf

@@ -1,3 +1,5 @@
+# $ custom_build_script
+
 # --- Blacklist ---
 
 # >> General

+ 3 - 0
List/ip/apple_services.conf → Source/ip/apple_services.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Apple IP
+# $ meta_description This file contains IPs owned/used by Apple, Inc.
+
 IP-CIDR,17.0.0.0/8,no-resolve
 IP-CIDR,63.92.224.0/19,no-resolve
 IP-CIDR,65.199.22.0/23,no-resolve

+ 4 - 0
Source/ip/domestic.conf

@@ -0,0 +1,4 @@
+# $ meta_title Sukka's Ruleset - Mainland China Supplement CIDR
+# $ meta_description This file contains IPs broadcast inside Mainland China.
+
+IP-CIDR,162.14.0.0/18,no-resolve

+ 3 - 0
List/ip/download.conf → Source/ip/download.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Large Files Hosting IPs
+# $ meta_description This file contains IPs for software updating & large file hosting.
+
 # >> MEGA
 
 IP-CIDR,185.206.24.0/22,no-resolve

+ 2 - 1
List/ip/neteasemusic.conf → Source/ip/neteasemusic.conf

@@ -1,4 +1,5 @@
-# >> NeteaseMusic
+# $ meta_title Sukka's Ruleset - Netease Music IPs
+# $ meta_description This file contains IPs used by Netease Music.
 
 IP-CIDR,39.105.63.80/32,no-resolve
 IP-CIDR,45.254.48.1/32,no-resolve

+ 2 - 0
Source/ip/reject.conf

@@ -1,3 +1,5 @@
+# $ custom_build_script
+
 # --- AD Block ---
 
 # >> iQiyi

+ 2 - 1
List/ip/stream.conf → Source/ip/stream.conf

@@ -1,4 +1,5 @@
-# --- Stream Service ---
+# $ meta_title Sukka's Ruleset - Stream Services IPs
+# $ meta_description This file contains IPs used by popular stream services.
 
 # >> Netflix
 IP-CIDR,23.246.18.0/23,no-resolve

+ 3 - 0
List/non_ip/apple_services.conf → Source/non_ip/apple_services.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Apple Domains
+# $ meta_description This file contains domains of Apple, Inc that doesn't have PoP inside the Mainland China.
+
 # >> Apple
 DOMAIN-SUFFIX,aaplimg.com
 DOMAIN-SUFFIX,apple-dns.net

+ 2 - 0
Source/non_ip/cdn.conf

@@ -1,3 +1,5 @@
+# $ custom_build_script
+
 # >> GitHub Pages
 DOMAIN-SUFFIX,github.io
 # >> GitHub

+ 3 - 0
List/non_ip/direct.conf → Source/non_ip/direct.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Direct Rules
+# $ meta_description This file contains domains and process that should not be proxied.
+
 # >> AdGuard
 DOMAIN,injections.adguard.org
 DOMAIN,local.adguard.org

+ 2 - 0
Source/non_ip/domestic.conf

@@ -1,3 +1,5 @@
+# $ custom_build_script
+
 # >> Akamai
 DOMAIN-SUFFIX,akadns.net
 

+ 3 - 2
List/non_ip/global.conf → Source/non_ip/global.conf

@@ -1,4 +1,5 @@
-# --- General Global Services ---
+# $ meta_title Sukka's Ruleset - General Global Services
+# $ meta_description This file contains rules for services that are not available inside the Mainland China.
 
 # >> Apple
 DOMAIN-SUFFIX,appsto.re
@@ -90,4 +91,4 @@ DOMAIN-KEYWORD,github
 
 USER-AGENT,Roam*
 
-# --- End of General Global Services Section ---
+# --- End of General Global Services Section ---

+ 2 - 1
List/non_ip/global_plus.conf → Source/non_ip/global_plus.conf

@@ -1,4 +1,5 @@
-# --- Enhanced Global Services ---
+# $ meta_title Sukka's Ruleset - Enhanced Global Services
+# $ meta_description This file contains rules for services that are not available inside the Mainland China.
 
 # >> Cloudflare
 DOMAIN-SUFFIX,cloudflareresolve.com

+ 2 - 0
List/non_ip/my_direct.conf → Source/non_ip/my_direct.conf

@@ -1,3 +1,5 @@
+# $ meta_title Sukka's Ruleset - Direct
+
 DOMAIN-SUFFIX,torrentmac.net
 DOMAIN-SUFFIX,download.555mac.com
 DOMAIN-KEYWORD,mac-torrent-download

+ 2 - 0
List/non_ip/my_proxy.conf → Source/non_ip/my_proxy.conf

@@ -1,3 +1,5 @@
+# $ meta_title Sukka's Ruleset - Proxy
+
 DOMAIN-SUFFIX,mikuclub.xyz
 DOMAIN-SUFFIX,mikuclub.cn
 DOMAIN-SUFFIX,saucenao.com

+ 2 - 0
List/non_ip/my_reject.conf → Source/non_ip/my_reject.conf

@@ -1,3 +1,5 @@
+# $ meta_title Sukka's Ruleset - Reject
+
 DOMAIN,download.parallels.com
 DOMAIN,update.parallels.com
 DOMAIN,desktop.parallels.com

+ 2 - 1
List/non_ip/neteasemusic.conf → Source/non_ip/neteasemusic.conf

@@ -1,4 +1,5 @@
-# >> NeteaseMusic
+# $ meta_title Sukka's Ruleset - Netease Music
+# $ meta_description This file contains rules for Netease Music.
 
 USER-AGENT,%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90
 USER-AGENT,NeteaseMusic*

+ 3 - 0
List/non_ip/reject-no-drop.conf → Source/non_ip/reject-no-drop.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Reject No Drop
+# $ meta_description This file This file contains rules for domain should be used with REJECT-NO-DROP policy.
+
 # Block YouTube QUIC
 AND,((PROTOCOL,UDP), (DOMAIN-SUFFIX,googlevideo.com))
 # Block Bilibili/Douyu P2P CDN

+ 3 - 0
List/non_ip/reject.conf → Source/non_ip/reject.conf

@@ -1,3 +1,6 @@
+# $ meta_title Sukka's Ruleset - Reject Domains
+# $ meta_description The ruleset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining
+
 # --- Blacklist ---
 
 # >> Crypto Coin Hive

+ 3 - 1
List/non_ip/sogouinput.conf → Source/non_ip/sogouinput.conf

@@ -1,4 +1,6 @@
-# >> Sogou Input
+# $ meta_title Sukka's Ruleset - Sogou Input
+# $ meta_description This file contains rules for Sogou Input.
+
 PROCESS-NAME,SogouInput
 PROCESS-NAME,SOgouTaskManager
 PROCESS-NAME,SogouServices

+ 2 - 1
List/non_ip/stream.conf → Source/non_ip/stream.conf

@@ -1,4 +1,5 @@
-# --- Stream Service ---
+# $ meta_title Sukka's Ruleset - Stream Services
+# $ meta_description This file contains rules for popular stream services.
 
 # >> 4gtv
 DOMAIN-SUFFIX,4gtv.tv

+ 3 - 1
List/non_ip/telegram.conf → Source/non_ip/telegram.conf

@@ -1,4 +1,6 @@
-# > Telegram
+# $ meta_title Sukka's Ruleset - Telegram Domains
+# $ meta_description This file contains domains used by Telegram Messenger.
+
 DOMAIN-SUFFIX,t.me
 DOMAIN-SUFFIX,tx.me
 DOMAIN-SUFFIX,tdesktop.com

+ 12 - 0
package.json

@@ -5,6 +5,7 @@
   "description": "",
   "scripts": {
     "build": "wireit",
+    "build:common": "wireit",
     "download-previous-build": "wireit",
     "build:anti-bogus-domain": "wireit",
     "build:apple-cdn": "wireit",
@@ -25,6 +26,12 @@
     "download-previous-build": {
       "command": "node ./Build/download-previous-build.js"
     },
+    "build:common": {
+      "command": "node ./Build/build.js",
+      "dependencies": [
+        "download-previous-build"
+      ]
+    },
     "build:anti-bogus-domain": {
       "command": "node ./Build/build-anti-bogus-domain.js",
       "dependencies": [
@@ -95,6 +102,7 @@
     "build:public": {
       "command": "node ./Build/build-public.js",
       "dependencies": [
+        "build:common",
         "build:anti-bogus-domain",
         "build:apple-cdn",
         "build:cdn-conf",
@@ -118,6 +126,7 @@
     },
     "build": {
       "dependencies": [
+        "build:common",
         "build:anti-bogus-domain",
         "build:apple-cdn",
         "build:cdn-conf",
@@ -145,8 +154,11 @@
     "@sukka/listdir": "^0.2.0",
     "@vercel/fetch-retry": "^5.1.3",
     "async-sema": "^3.1.1",
+    "ci-info": "^3.8.0",
     "cidr-tools-wasm": "^0.0.11",
     "fs-extra": "^11.1.1",
+    "mnemonist": "^0.39.5",
+    "path-scurry": "^1.10.1",
     "picocolors": "^1.0.0",
     "table": "^6.8.1",
     "tar": "^6.1.15",

+ 43 - 6
pnpm-lock.yaml

@@ -25,12 +25,21 @@ dependencies:
   async-sema:
     specifier: ^3.1.1
     version: 3.1.1
+  ci-info:
+    specifier: ^3.8.0
+    version: 3.8.0
   cidr-tools-wasm:
     specifier: ^0.0.11
     version: 0.0.11
   fs-extra:
     specifier: ^11.1.1
     version: 11.1.1
+  mnemonist:
+    specifier: ^0.39.5
+    version: 0.39.5
+  path-scurry:
+    specifier: ^1.10.1
+    version: 1.10.1
   picocolors:
     specifier: ^1.0.0
     version: 1.0.0
@@ -190,15 +199,15 @@ packages:
       fastq: 1.13.0
     dev: true
 
-  /@nolyfill/has@1.0.11:
-    resolution: {integrity: sha512-Q2QNYUzZxW4/FzI57EGuxQF6PVO+LJajvcsRGTj26FU4z5rmXouaTGMas/2OreYupieIGMeI5dNEJwXS/Wy2kQ==}
+  /@nolyfill/has@1.0.21:
+    resolution: {integrity: sha512-Sf8iFaegjGp29hQVQjIc+nDR0uWqGkHsFC3jsUigFwGjpafgMaBtL++DpTU9jYAKDJEvslR1szl8qJjNGlhgcw==}
     engines: {node: '>=12.4.0'}
     dependencies:
-      '@nolyfill/shared': 1.0.11
+      '@nolyfill/shared': 1.0.21
     dev: true
 
-  /@nolyfill/shared@1.0.11:
-    resolution: {integrity: sha512-MVtVsoUIbg93Bs33Nc7JvOv/ePyUjUMXorrTtTdjPpLn2t8HmbklZvk2ur0np0thyI9OWC5mNQHjR2l0kwJUNQ==}
+  /@nolyfill/shared@1.0.21:
+    resolution: {integrity: sha512-qDc/NoaFU23E0hhiDPeUrvWzTXIPE+RbvRQtRWSeHHNmCIgYI9HS1jKzNYNJxv4jvZ/1VmM3L6rNVxbj+LBMNA==}
     dev: true
 
   /@remusao/guess-url-type@1.2.1:
@@ -459,6 +468,11 @@ packages:
     engines: {node: '>=10'}
     dev: false
 
+  /ci-info@3.8.0:
+    resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
+    engines: {node: '>=8'}
+    dev: false
+
   /cidr-tools-wasm@0.0.11:
     resolution: {integrity: sha512-WUnooVHC+0/uwG+/5QZT6auR2Gzga+BFkwyQiKki8uZnVHOCn3gEt+FVjHg/7pdXsCbzGsDSMGkZ31ZqIkUrrw==}
     dev: false
@@ -964,7 +978,7 @@ packages:
   /is-core-module@2.12.1:
     resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
     dependencies:
-      has: /@nolyfill/has@1.0.11
+      has: /@nolyfill/has@1.0.21
     dev: true
 
   /is-extglob@2.1.1:
@@ -1075,6 +1089,11 @@ packages:
       get-func-name: 2.0.0
     dev: true
 
+  /lru-cache@10.0.1:
+    resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
+    engines: {node: 14 || >=16.14}
+    dev: false
+
   /lru-cache@6.0.0:
     resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
     engines: {node: '>=10'}
@@ -1134,6 +1153,12 @@ packages:
     hasBin: true
     dev: false
 
+  /mnemonist@0.39.5:
+    resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==}
+    dependencies:
+      obliterator: 2.0.4
+    dev: false
+
   /mocha@10.2.0:
     resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==}
     engines: {node: '>= 14.0.0'}
@@ -1196,6 +1221,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /obliterator@2.0.4:
+    resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==}
+    dev: false
+
   /once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
     dependencies:
@@ -1254,6 +1283,14 @@ packages:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
     dev: true
 
+  /path-scurry@1.10.1:
+    resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    dependencies:
+      lru-cache: 10.0.1
+      minipass: 5.0.0
+    dev: false
+
   /pathval@1.1.1:
     resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
     dev: true