build.js 3.6 KB

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