build-common.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. 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. 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. console.log('Processing', sourcePath);
  45. const lines: string[] = [];
  46. let title = '';
  47. const descriptions: string[] = [];
  48. try {
  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. } catch (e) {
  67. console.error('Error processing', sourcePath);
  68. console.trace(e);
  69. }
  70. return [title, descriptions, lines] as const;
  71. };
  72. async function transformDomainset(sourcePath: string, relativePath: string) {
  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. ...SHARED_DESCRIPTION,
  79. ...(
  80. descriptions.length
  81. ? ['', ...descriptions]
  82. : []
  83. )
  84. ];
  85. return Promise.all(createRuleset(
  86. title,
  87. description,
  88. new Date(),
  89. deduped,
  90. 'domainset',
  91. path.resolve(outputSurgeDir, relativePath),
  92. path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
  93. ));
  94. }
  95. /**
  96. * Output Surge RULE-SET and Clash classical text format
  97. */
  98. async function transformRuleset(sourcePath: string, relativePath: string) {
  99. const res = await processFile(sourcePath);
  100. if (!res) return;
  101. const [title, descriptions, lines] = res;
  102. const description = [
  103. ...SHARED_DESCRIPTION,
  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. }