build-common.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // @ts-check
  2. import * as path from 'path';
  3. import { PathScurry } from 'path-scurry';
  4. import { readFileByLine } from './lib/fetch-remote-text-by-line';
  5. import { processLine } from './lib/process-line';
  6. import { createRuleset } from './lib/create-file';
  7. import { domainDeduper } from './lib/domain-deduper';
  8. import { task } from './lib/trace-runner';
  9. const MAGIC_COMMAND_SKIP = '# $ custom_build_script';
  10. const MAGIC_COMMAND_TITLE = '# $ meta_title ';
  11. const MAGIC_COMMAND_DESCRIPTION = '# $ meta_description ';
  12. const sourceDir = path.resolve(__dirname, '../Source');
  13. const outputSurgeDir = path.resolve(__dirname, '../List');
  14. const outputClashDir = path.resolve(__dirname, '../Clash');
  15. export const buildCommon = task(__filename, async () => {
  16. /** @type {Promise<unknown>[]} */
  17. const promises = [];
  18. const pw = new PathScurry(sourceDir);
  19. for await (const entry of pw) {
  20. if (entry.isFile()) {
  21. if (path.extname(entry.name) === '.js') {
  22. continue;
  23. }
  24. const relativePath = entry.relative();
  25. if (relativePath.startsWith('domainset/')) {
  26. promises.push(transformDomainset(entry.fullpath(), relativePath));
  27. continue;
  28. }
  29. if (
  30. relativePath.startsWith('ip/')
  31. || relativePath.startsWith('non_ip/')
  32. ) {
  33. promises.push(transformRuleset(entry.fullpath(), relativePath));
  34. continue;
  35. }
  36. }
  37. }
  38. return Promise.all(promises);
  39. });
  40. if (import.meta.main) {
  41. buildCommon();
  42. }
  43. const processFile = async (sourcePath: string) => {
  44. /** @type {string[]} */
  45. const lines = [];
  46. let title = '';
  47. /** @type {string[]} */
  48. const descriptions = [];
  49. for await (const line of readFileByLine(sourcePath)) {
  50. if (line === MAGIC_COMMAND_SKIP) {
  51. return;
  52. }
  53. if (line.startsWith(MAGIC_COMMAND_TITLE)) {
  54. title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
  55. continue;
  56. }
  57. if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
  58. descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
  59. continue;
  60. }
  61. const l = processLine(line);
  62. if (l) {
  63. lines.push(l);
  64. }
  65. }
  66. return [title, descriptions, lines] as const;
  67. };
  68. async function transformDomainset(sourcePath: string, relativePath: string) {
  69. const res = await processFile(sourcePath);
  70. if (!res) return;
  71. const [title, descriptions, lines] = res;
  72. const deduped = domainDeduper(lines);
  73. const description = [
  74. 'License: AGPL 3.0',
  75. 'Homepage: https://ruleset.skk.moe',
  76. 'GitHub: https://github.com/SukkaW/Surge',
  77. ...(
  78. descriptions.length
  79. ? ['', ...descriptions]
  80. : []
  81. )
  82. ];
  83. return Promise.all(createRuleset(
  84. title,
  85. description,
  86. new Date(),
  87. deduped,
  88. 'domainset',
  89. path.resolve(outputSurgeDir, relativePath),
  90. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  91. ));
  92. }
  93. /**
  94. * Output Surge RULE-SET and Clash classical text format
  95. */
  96. async function transformRuleset(sourcePath: string, relativePath: string) {
  97. const res = await processFile(sourcePath);
  98. if (!res) return;
  99. const [title, descriptions, lines] = res;
  100. const description = [
  101. 'License: AGPL 3.0',
  102. 'Homepage: https://ruleset.skk.moe',
  103. 'GitHub: https://github.com/SukkaW/Surge',
  104. ...(
  105. descriptions.length
  106. ? ['', ...descriptions]
  107. : []
  108. )
  109. ];
  110. return Promise.all(createRuleset(
  111. title,
  112. description,
  113. new Date(),
  114. lines,
  115. 'ruleset',
  116. path.resolve(outputSurgeDir, relativePath),
  117. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  118. ));
  119. }