singbox.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { BaseWriteStrategy } from './base';
  2. import { appendArrayInPlace } from 'foxts/append-array-in-place';
  3. import { noop } from 'foxts/noop';
  4. import { fastIpVersion, withIdentityContent } from '../misc';
  5. import stringify from 'json-stringify-pretty-compact';
  6. import { OUTPUT_SINGBOX_DIR } from '../../constants/dir';
  7. interface SingboxHeadlessRule {
  8. domain: string[], // this_ruleset_is_made_by_sukkaw.ruleset.skk.moe
  9. domain_suffix: string[], // this_ruleset_is_made_by_sukkaw.ruleset.skk.moe
  10. domain_keyword?: string[],
  11. domain_regex?: string[],
  12. source_ip_cidr?: string[],
  13. ip_cidr?: string[],
  14. source_port?: number[],
  15. source_port_range?: string[],
  16. port?: number[],
  17. port_range?: string[],
  18. process_name?: string[],
  19. process_path?: string[],
  20. network?: string[]
  21. }
  22. export interface SingboxSourceFormat {
  23. version: 2 | number & {},
  24. rules: SingboxHeadlessRule[]
  25. }
  26. export class SingboxSource extends BaseWriteStrategy {
  27. public readonly name = 'singbox';
  28. readonly fileExtension = 'json';
  29. static readonly jsonToLines = (json: unknown): string[] => stringify(json).split('\n');
  30. private singbox: SingboxHeadlessRule = {
  31. domain: ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe'],
  32. domain_suffix: ['this_ruleset_is_made_by_sukkaw.ruleset.skk.moe']
  33. };
  34. protected get result() {
  35. return SingboxSource.jsonToLines({
  36. version: 2,
  37. rules: [this.singbox]
  38. });
  39. }
  40. constructor(
  41. /** Since sing-box only have one format that does not reflect type, we need to specify it */
  42. public type: 'domainset' | 'non_ip' | 'ip' /* | (string & {}) */,
  43. public readonly outputDir = OUTPUT_SINGBOX_DIR
  44. ) {
  45. super(outputDir);
  46. }
  47. withPadding = withIdentityContent;
  48. writeDomain(domain: string): void {
  49. this.singbox.domain.push(domain);
  50. }
  51. writeDomainSuffix(domain: string): void {
  52. this.singbox.domain_suffix.push(domain);
  53. }
  54. writeDomainKeywords(keyword: Set<string>): void {
  55. appendArrayInPlace(
  56. this.singbox.domain_keyword ??= [],
  57. Array.from(keyword)
  58. );
  59. }
  60. writeDomainWildcards(wildcard: Set<string>): void {
  61. appendArrayInPlace(
  62. this.singbox.domain_regex ??= [],
  63. Array.from(wildcard, SingboxSource.domainWildCardToRegex)
  64. );
  65. }
  66. writeUserAgents = noop;
  67. writeProcessNames(processName: Set<string>): void {
  68. appendArrayInPlace(
  69. this.singbox.process_name ??= [],
  70. Array.from(processName)
  71. );
  72. }
  73. writeProcessPaths(processPath: Set<string>): void {
  74. appendArrayInPlace(
  75. this.singbox.process_path ??= [],
  76. Array.from(processPath)
  77. );
  78. }
  79. writeUrlRegexes = noop;
  80. writeIpCidrs(ipCidr: string[]): void {
  81. appendArrayInPlace(
  82. this.singbox.ip_cidr ??= [],
  83. ipCidr
  84. );
  85. }
  86. writeIpCidr6s(ipCidr6: string[]): void {
  87. appendArrayInPlace(
  88. this.singbox.ip_cidr ??= [],
  89. ipCidr6
  90. );
  91. }
  92. writeGeoip = noop;
  93. writeIpAsns = noop;
  94. writeSourceIpCidrs(sourceIpCidr: string[]): void {
  95. this.singbox.source_ip_cidr ??= [];
  96. for (let i = 0, len = sourceIpCidr.length; i < len; i++) {
  97. const value = sourceIpCidr[i];
  98. if (value.includes('/')) {
  99. this.singbox.source_ip_cidr.push(value);
  100. continue;
  101. }
  102. const v = fastIpVersion(value);
  103. if (v === 4) {
  104. this.singbox.source_ip_cidr.push(`${value}/32`);
  105. continue;
  106. }
  107. if (v === 6) {
  108. this.singbox.source_ip_cidr.push(`${value}/128`);
  109. continue;
  110. }
  111. }
  112. }
  113. writeSourcePorts(port: Set<string>): void {
  114. this.singbox.source_port ??= [];
  115. for (const i of port) {
  116. const tmp = Number(i);
  117. if (!Number.isNaN(tmp)) {
  118. this.singbox.source_port.push(tmp);
  119. }
  120. }
  121. }
  122. writeDestinationPorts(port: Set<string>): void {
  123. this.singbox.port ??= [];
  124. for (const i of port) {
  125. const tmp = Number(i);
  126. if (!Number.isNaN(tmp)) {
  127. this.singbox.port.push(tmp);
  128. }
  129. }
  130. }
  131. writeProtocols(protocol: Set<string>): void {
  132. this.singbox.network ??= [];
  133. // protocol has already be normalized and will only be uppercase
  134. if (protocol.has('UDP')) {
  135. this.singbox.network.push('udp');
  136. }
  137. if (protocol.has('TCP')) {
  138. this.singbox.network.push('tcp');
  139. }
  140. }
  141. writeOtherRules = noop;
  142. }