build-common.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // @ts-check
  2. import * as path from 'path';
  3. import { PathScurry } from 'path-scurry';
  4. import { readFileByLine } from './lib/fetch-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. import { SHARED_DESCRIPTION } from './lib/constants';
  10. const MAGIC_COMMAND_SKIP = '# $ custom_build_script';
  11. const MAGIC_COMMAND_TITLE = '# $ meta_title ';
  12. const MAGIC_COMMAND_DESCRIPTION = '# $ meta_description ';
  13. const sourceDir = path.resolve(import.meta.dir, '../Source');
  14. const outputSurgeDir = path.resolve(import.meta.dir, '../List');
  15. const outputClashDir = path.resolve(import.meta.dir, '../Clash');
  16. export const buildCommon = task(import.meta.path, async () => {
  17. const promises: Array<Promise<unknown>> = [];
  18. const pw = new PathScurry(sourceDir);
  19. for await (const entry of pw) {
  20. if (!entry.isFile()) {
  21. continue;
  22. }
  23. const extname = path.extname(entry.name);
  24. if (extname === '.js' || extname === '.ts') {
  25. continue;
  26. }
  27. const relativePath = entry.relative();
  28. if (relativePath.startsWith('domainset/')) {
  29. promises.push(transformDomainset(entry.fullpath(), relativePath));
  30. continue;
  31. }
  32. if (
  33. relativePath.startsWith('ip/')
  34. || relativePath.startsWith('non_ip/')
  35. ) {
  36. promises.push(transformRuleset(entry.fullpath(), relativePath));
  37. continue;
  38. }
  39. }
  40. return Promise.all(promises);
  41. });
  42. if (import.meta.main) {
  43. buildCommon();
  44. }
  45. const processFile = async (sourcePath: string) => {
  46. console.log('Processing', sourcePath);
  47. const lines: string[] = [];
  48. let title = '';
  49. const descriptions: string[] = [];
  50. try {
  51. for await (const line of readFileByLine(sourcePath)) {
  52. if (line === MAGIC_COMMAND_SKIP) {
  53. return;
  54. }
  55. if (line.startsWith(MAGIC_COMMAND_TITLE)) {
  56. title = line.slice(MAGIC_COMMAND_TITLE.length).trim();
  57. continue;
  58. }
  59. if (line.startsWith(MAGIC_COMMAND_DESCRIPTION)) {
  60. descriptions.push(line.slice(MAGIC_COMMAND_DESCRIPTION.length).trim());
  61. continue;
  62. }
  63. const l = processLine(line);
  64. if (l) {
  65. lines.push(l);
  66. }
  67. }
  68. } catch (e) {
  69. console.error('Error processing', sourcePath);
  70. console.trace(e);
  71. }
  72. return [title, descriptions, lines] as const;
  73. };
  74. async function transformDomainset(sourcePath: string, relativePath: string) {
  75. const res = await processFile(sourcePath);
  76. if (!res) return;
  77. const [title, descriptions, lines] = res;
  78. const deduped = domainDeduper(lines);
  79. const description = [
  80. ...SHARED_DESCRIPTION,
  81. ...(
  82. descriptions.length
  83. ? ['', ...descriptions]
  84. : []
  85. )
  86. ];
  87. return 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. async function transformRuleset(sourcePath: string, relativePath: string) {
  101. const res = await processFile(sourcePath);
  102. if (!res) return;
  103. const [title, descriptions, lines] = res;
  104. const description = [
  105. ...SHARED_DESCRIPTION,
  106. ...(
  107. descriptions.length
  108. ? ['', ...descriptions]
  109. : []
  110. )
  111. ];
  112. return Promise.all(createRuleset(
  113. title,
  114. description,
  115. new Date(),
  116. lines,
  117. 'ruleset',
  118. path.resolve(outputSurgeDir, relativePath),
  119. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  120. ));
  121. }