build-domestic-direct-lan-ruleset-dns-mapping-module.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // @ts-check
  2. import path from 'node:path';
  3. import { DOMESTICS } from '../Source/non_ip/domestic';
  4. import { DIRECTS, LANS } from '../Source/non_ip/direct';
  5. import { readFileIntoProcessedArray } from './lib/fetch-text-by-line';
  6. import { compareAndWriteFile } from './lib/create-file';
  7. import { task } from './trace';
  8. import { SHARED_DESCRIPTION } from './lib/constants';
  9. import { createMemoizedPromise } from './lib/memo-promise';
  10. import * as yaml from 'yaml';
  11. import { appendArrayInPlace } from './lib/append-array-in-place';
  12. import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir';
  13. import { RulesetOutput } from './lib/create-file';
  14. const getRule = (domain: string) => {
  15. switch (domain[0]) {
  16. case '+':
  17. case '$':
  18. return `DOMAIN-SUFFIX,${domain.slice(1)}`;
  19. default:
  20. return `DOMAIN-SUFFIX,${domain}`;
  21. }
  22. };
  23. export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(async () => {
  24. const domestics = await readFileIntoProcessedArray(path.join(SOURCE_DIR, 'non_ip/domestic.conf'));
  25. const directs = await readFileIntoProcessedArray(path.resolve(SOURCE_DIR, 'non_ip/direct.conf'));
  26. const lans: string[] = [];
  27. Object.entries(DOMESTICS).forEach(([, { domains }]) => {
  28. appendArrayInPlace(domestics, domains.map(getRule));
  29. });
  30. Object.entries(DIRECTS).forEach(([, { domains }]) => {
  31. appendArrayInPlace(directs, domains.map(getRule));
  32. });
  33. Object.entries(LANS).forEach(([, { domains }]) => {
  34. appendArrayInPlace(lans, domains.map(getRule));
  35. });
  36. return [domestics, directs, lans] as const;
  37. });
  38. export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => {
  39. const [domestics, directs, lans] = await getDomesticAndDirectDomainsRulesetPromise();
  40. const dataset = Object.entries(DOMESTICS);
  41. appendArrayInPlace(dataset, Object.entries(DIRECTS));
  42. appendArrayInPlace(dataset, Object.entries(LANS));
  43. return Promise.all([
  44. new RulesetOutput(span, 'domestic', 'non_ip')
  45. .withTitle('Sukka\'s Ruleset - Domestic Domains')
  46. .withDescription([
  47. ...SHARED_DESCRIPTION,
  48. '',
  49. 'This file contains known addresses that are avaliable in the Mainland China.'
  50. ])
  51. .addFromRuleset(domestics)
  52. .write(),
  53. new RulesetOutput(span, 'direct', 'non_ip')
  54. .withTitle('Sukka\'s Ruleset - Direct Rules')
  55. .withDescription([
  56. ...SHARED_DESCRIPTION,
  57. '',
  58. 'This file contains domains and process that should not be proxied.'
  59. ])
  60. .addFromRuleset(directs)
  61. .write(),
  62. new RulesetOutput(span, 'lan', 'non_ip')
  63. .withTitle('Sukka\'s Ruleset - LAN')
  64. .withDescription([
  65. ...SHARED_DESCRIPTION,
  66. '',
  67. 'This file includes rules for LAN DOMAIN and reserved TLDs.'
  68. ])
  69. .addFromRuleset(lans)
  70. .write(),
  71. compareAndWriteFile(
  72. span,
  73. [
  74. '#!name=[Sukka] Local DNS Mapping',
  75. `#!desc=Last Updated: ${new Date().toISOString()}`,
  76. '',
  77. '[Host]',
  78. ...dataset.flatMap(([, { domains, dns, hosts }]) => [
  79. ...Object.entries(hosts).flatMap(([dns, ips]: [dns: string, ips: string[]]) => `${dns} = ${ips.join(', ')}`),
  80. ...domains.flatMap((domain) => {
  81. if (domain[0] === '$') {
  82. return [
  83. `${domain.slice(1)} = server:${dns}`
  84. ];
  85. }
  86. if (domain[0] === '+') {
  87. return [
  88. `*.${domain.slice(1)} = server:${dns}`
  89. ];
  90. }
  91. return [
  92. `${domain} = server:${dns}`,
  93. `*.${domain} = server:${dns}`
  94. ];
  95. })
  96. ])
  97. ],
  98. path.resolve(OUTPUT_MODULES_DIR, 'sukka_local_dns_mapping.sgmodule')
  99. ),
  100. compareAndWriteFile(
  101. span,
  102. yaml.stringify(
  103. {
  104. dns: {
  105. 'nameserver-policy': dataset.reduce<Record<string, string | string[]>>(
  106. (acc, [, { domains, dns }]) => {
  107. domains.forEach((domain) => {
  108. let domainWildcard = domain;
  109. if (domain[0] === '$') {
  110. domainWildcard = domain.slice(1);
  111. } else if (domain[0] === '+') {
  112. domainWildcard = `*.${domain.slice(1)}`;
  113. } else {
  114. domainWildcard = `+.${domain}`;
  115. }
  116. acc[domainWildcard] = dns === 'system'
  117. ? [
  118. 'system://',
  119. 'system',
  120. 'dhcp://system'
  121. ]
  122. : dns;
  123. });
  124. return acc;
  125. },
  126. {}
  127. )
  128. },
  129. hosts: dataset.reduce<Record<string, string>>(
  130. (acc, [, { domains, dns, ...rest }]) => {
  131. if ('hosts' in rest) {
  132. Object.assign(acc, rest.hosts);
  133. }
  134. return acc;
  135. },
  136. {}
  137. )
  138. },
  139. { version: '1.1' }
  140. ).split('\n'),
  141. path.join(OUTPUT_INTERNAL_DIR, 'clash_nameserver_policy.yaml')
  142. )
  143. ]);
  144. });