build.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // @ts-check
  2. const path = require('path');
  3. const { PathScurry } = require('path-scurry');
  4. const { readFileByLine } = require('./lib/fetch-remote-text-by-line');
  5. const { processLine } = require('./lib/process-line');
  6. const { createRuleset } = require('./lib/create-file');
  7. const { domainDeduper } = require('./lib/domain-deduper');
  8. const { runner } = require('./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. runner(__filename, async () => {
  16. /** @type {Promise<void>[]} */
  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. await Promise.all(promises);
  39. });
  40. /**
  41. * @param {string} sourcePath
  42. */
  43. const processFile = async (sourcePath) => {
  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 /** @type {const} */ ([title, descriptions, lines]);
  67. };
  68. /**
  69. * @param {string} sourcePath
  70. * @param {string} relativePath
  71. */
  72. async function transformDomainset(sourcePath, relativePath) {
  73. const res = await processFile(sourcePath);
  74. if (!res) return;
  75. const [title, descriptions, lines] = res;
  76. const deduped = domainDeduper(lines);
  77. const description = [
  78. 'License: AGPL 3.0',
  79. 'Homepage: https://ruleset.skk.moe',
  80. 'GitHub: https://github.com/SukkaW/Surge',
  81. ...(
  82. descriptions.length
  83. ? ['', ...descriptions]
  84. : []
  85. )
  86. ];
  87. await Promise.all(createRuleset(
  88. title,
  89. description,
  90. new Date(),
  91. deduped,
  92. 'domainset',
  93. path.resolve(outputSurgeDir, relativePath),
  94. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  95. ));
  96. }
  97. /**
  98. * Output Surge RULE-SET and Clash classical text format
  99. *
  100. * @param {string} sourcePath
  101. * @param {string} relativePath
  102. */
  103. async function transformRuleset(sourcePath, relativePath) {
  104. const res = await processFile(sourcePath);
  105. if (!res) return;
  106. const [title, descriptions, lines] = res;
  107. const description = [
  108. 'License: AGPL 3.0',
  109. 'Homepage: https://ruleset.skk.moe',
  110. 'GitHub: https://github.com/SukkaW/Surge',
  111. ...(
  112. descriptions.length
  113. ? ['', ...descriptions]
  114. : []
  115. )
  116. ];
  117. await Promise.all(createRuleset(
  118. title,
  119. description,
  120. new Date(),
  121. lines,
  122. 'ruleset',
  123. path.resolve(outputSurgeDir, relativePath),
  124. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  125. ));
  126. }