nodelistparser 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,7 +52,7 @@ interface TrojanBasicConfig extends SharedConfigBase, TlsSharedConfig {
52
52
  /** udp-relay */
53
53
  udp: boolean;
54
54
  }
55
- interface TuicConfig extends SharedConfigBase {
55
+ interface TuicConfig extends SharedConfigBase, TlsSharedConfig {
56
56
  type: 'tuic';
57
57
  sni: string;
58
58
  uuid: string;
@@ -152,6 +152,7 @@ declare function encode(config: SupportedConfig): {
152
152
  down?: undefined;
153
153
  } | {
154
154
  tfo: boolean | undefined;
155
+ 'skip-cert-verify': boolean;
155
156
  udp: boolean;
156
157
  token: string;
157
158
  name: string;
@@ -172,9 +173,9 @@ declare function encode(config: SupportedConfig): {
172
173
  ports?: undefined;
173
174
  password?: undefined;
174
175
  down?: undefined;
175
- 'skip-cert-verify'?: undefined;
176
176
  } | {
177
177
  tfo: boolean | undefined;
178
+ 'skip-cert-verify': boolean;
178
179
  udp: boolean;
179
180
  password: string;
180
181
  name: string;
@@ -194,7 +195,6 @@ declare function encode(config: SupportedConfig): {
194
195
  network?: undefined;
195
196
  ports?: undefined;
196
197
  down?: undefined;
197
- 'skip-cert-verify'?: undefined;
198
198
  } | {
199
199
  tfo: boolean | undefined;
200
200
  name: string;
package/dist/cjs/index.js CHANGED
@@ -1,591 +1 @@
1
- var guard = require('foxts/guard');
2
-
3
- const boolean = (text)=>text === 'true';
4
- const number = Number;
5
- const comma = (text)=>text.split(',').map((piece)=>piece.trim());
6
- function assign(text) {
7
- const signIndex = text.indexOf('=');
8
- return signIndex === -1 ? [
9
- '',
10
- ''
11
- ] : [
12
- text.slice(0, signIndex).trim(),
13
- text.slice(signIndex + 1).trim()
14
- ];
15
- }
16
-
17
- const boolKeys = new Set([
18
- 'udp-relay',
19
- 'tfo',
20
- 'reuse',
21
- 'skip-cert-verify',
22
- 'tls',
23
- 'vmess-aead',
24
- 'ws'
25
- ]);
26
- const isProxyBoolKey = (key)=>boolKeys.has(key);
27
- const numKeys = new Set([
28
- 'version',
29
- 'download-bandwidth',
30
- 'port-hopping-interval',
31
- 'udp-port'
32
- ]);
33
- const isProxyNumKey = (key)=>numKeys.has(key);
34
- const arrKeys = new Set([]);
35
- const isProxyArrKey = (key)=>arrKeys.has(key);
36
- const strKeys = new Set([
37
- 'username',
38
- 'password',
39
- 'sni',
40
- 'encrypt-method',
41
- 'psk',
42
- 'obfs',
43
- 'obfs-host',
44
- 'uuid',
45
- 'alpn',
46
- 'block-quic',
47
- 'ws-path',
48
- 'ws-headers',
49
- 'port-hopping',
50
- 'token'
51
- ]);
52
- const isProxyStrKey = (key)=>strKeys.has(key);
53
- const UNSUPPORTED_VALUE = Symbol('unsupported');
54
- function decode$1(raw) {
55
- const parsePart = (part)=>{
56
- const [key, value] = assign(part);
57
- if (isProxyBoolKey(key)) {
58
- return [
59
- key,
60
- boolean(value)
61
- ];
62
- }
63
- if (isProxyNumKey(key)) {
64
- return [
65
- key,
66
- number(value)
67
- ];
68
- }
69
- if (isProxyArrKey(key)) {
70
- return [
71
- key,
72
- comma(value)
73
- ];
74
- }
75
- if (isProxyStrKey(key)) {
76
- if (value[0] === '"' && value.endsWith('"') || value[0] === '\'' && value.endsWith('\'')) {
77
- return [
78
- key,
79
- value.slice(1, -1)
80
- ];
81
- }
82
- return [
83
- key,
84
- value
85
- ];
86
- }
87
- return [
88
- key,
89
- UNSUPPORTED_VALUE
90
- ];
91
- };
92
- const [name, parts] = assign(raw);
93
- const [type, server, mayPort, ...rest] = comma(parts);
94
- const port = number(mayPort);
95
- const restDetails = Object.fromEntries(rest.map(parsePart));
96
- const shared = {
97
- raw,
98
- name,
99
- server,
100
- port,
101
- tfo: restDetails.tfo,
102
- blockQuic: restDetails['block-quic']
103
- };
104
- switch(type){
105
- case 'snell':
106
- {
107
- return {
108
- type: 'snell',
109
- psk: restDetails.psk,
110
- version: restDetails.version,
111
- reuse: restDetails.reuse,
112
- ...shared
113
- };
114
- }
115
- case 'ss':
116
- {
117
- return {
118
- type: 'ss',
119
- cipher: restDetails['encrypt-method'],
120
- password: restDetails.password,
121
- udp: restDetails['udp-relay'],
122
- obfs: restDetails.obfs,
123
- obfsHost: restDetails['obfs-host'],
124
- obfsUri: restDetails['obfs-uri'],
125
- udpPort: restDetails['udp-port'],
126
- ...shared
127
- };
128
- }
129
- case 'trojan':
130
- {
131
- return {
132
- type: 'trojan',
133
- password: restDetails.password,
134
- sni: restDetails.sni,
135
- skipCertVerify: restDetails['skip-cert-verify'],
136
- udp: restDetails['udp-relay'],
137
- ...shared
138
- };
139
- }
140
- case 'tuic':
141
- {
142
- return {
143
- type: 'tuic',
144
- sni: restDetails.sni,
145
- uuid: restDetails.uuid,
146
- alpn: restDetails.alpn,
147
- token: restDetails.token,
148
- ...shared
149
- };
150
- }
151
- case 'tuic-v5':
152
- {
153
- return {
154
- type: 'tuic-v5',
155
- uuid: restDetails.uuid,
156
- alpn: restDetails.alpn,
157
- password: restDetails.password,
158
- sni: restDetails.sni,
159
- skipCertVerify: restDetails['skip-cert-verify'],
160
- ...shared
161
- };
162
- }
163
- case 'socks5':
164
- {
165
- return {
166
- type: 'socks5',
167
- username: rest[0],
168
- password: rest[1],
169
- udp: restDetails['udp-relay'],
170
- ...shared
171
- };
172
- }
173
- case 'http':
174
- {
175
- return {
176
- type: 'http',
177
- username: rest[0],
178
- password: rest[1],
179
- ...shared
180
- };
181
- }
182
- case 'vmess':
183
- {
184
- return {
185
- type: 'vmess',
186
- username: restDetails.username,
187
- tls: restDetails.tls,
188
- vmessAead: restDetails['vmess-aead'],
189
- ws: restDetails.ws,
190
- wsPath: restDetails['ws-path'],
191
- wsHeaders: restDetails['ws-headers'],
192
- skipCertVerify: restDetails['skip-cert-verify'],
193
- udp: restDetails['udp-relay'],
194
- sni: restDetails.sni,
195
- ...shared
196
- };
197
- }
198
- case 'hysteria2':
199
- return {
200
- type: 'hysteria2',
201
- password: restDetails.password,
202
- skipCertVerify: restDetails['skip-cert-verify'],
203
- downloadBandwidth: restDetails['download-bandwidth'],
204
- portHopping: restDetails['port-hopping'],
205
- portHoppingInterval: restDetails['port-hopping-interval'],
206
- ...shared
207
- };
208
- default:
209
- throw new TypeError(`Unsupported type: ${type} (surge decode)`);
210
- }
211
- // console.log({
212
- // name, type, server, port, restDetails
213
- // });
214
- }
215
- const joinString = (arr)=>arr.filter(Boolean).join(', ');
216
- function encode$1(config) {
217
- const shared = [
218
- config.tfo && 'tfo=true',
219
- config.blockQuic && `block-quic=${config.blockQuic}`
220
- ];
221
- switch(config.type){
222
- case 'snell':
223
- return joinString([
224
- `${config.name} = snell, ${config.server}, ${config.port}, psk=${config.psk}, version=${config.version}, reuse=${config.reuse}`,
225
- ...shared
226
- ]);
227
- case 'ss':
228
- return joinString([
229
- `${config.name} = ss, ${config.server}, ${config.port}, encrypt-method=${config.cipher}, password=${config.password}`,
230
- config.udp && 'udp-relay=true',
231
- config.udpPort && `udp-port=${config.udpPort}`,
232
- config.obfs && `obfs=${config.obfs}`,
233
- config.obfsHost && `obfs-host=${config.obfsHost}`,
234
- config.obfsUri && `obfs-uri=${config.obfsUri}`,
235
- ...shared
236
- ]);
237
- case 'trojan':
238
- return joinString([
239
- `${config.name} = trojan, ${config.server}, ${config.port}, password=${config.password}`,
240
- config.sni && `sni=${config.sni}`,
241
- config.skipCertVerify && 'skip-cert-verify=true',
242
- ...shared,
243
- config.udp && 'udp-relay=true'
244
- ]);
245
- case 'tuic':
246
- return joinString([
247
- `${config.name} = tuic, ${config.server}, ${config.port}, sni=${config.sni}, uuid=${config.uuid}, alpn=${config.alpn}, token=${config.token}`,
248
- ...shared
249
- ]);
250
- case 'socks5':
251
- return joinString([
252
- `${config.name} = socks5, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
253
- config.udp && 'udp-relay=true',
254
- ...shared
255
- ]);
256
- case 'http':
257
- return joinString([
258
- `${config.name} = http, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
259
- // no udp support for http
260
- ...shared
261
- ]);
262
- case 'vmess':
263
- return joinString([
264
- `${config.name} = vmess, ${config.server}, ${config.port}`,
265
- `username=${config.username}`,
266
- `tls=${config.tls}`,
267
- `vmess-aead=${config.vmessAead}`,
268
- 'ws=true',
269
- config.wsPath && `ws-path=${config.wsPath[0] === '/' ? config.wsPath : `/${config.wsPath}`}`,
270
- config.wsHeaders && `ws-headers=${config.wsHeaders}`,
271
- `skip-cert-verify=${config.skipCertVerify}`,
272
- `tfo=${config.tfo}`,
273
- `udp-relay=${config.udp}`
274
- ]);
275
- case 'hysteria2':
276
- return joinString([
277
- `${config.name} = hysteria2, ${config.server}, ${config.port}`,
278
- `password=${config.password}`,
279
- `download-bandwidth=${config.downloadBandwidth}`,
280
- config.portHopping && `port-hopping="${config.portHopping}"`,
281
- config.portHoppingInterval && `port-hopping-interval=${config.portHoppingInterval}`,
282
- `skip-cert-verify=${config.skipCertVerify}`,
283
- ...shared
284
- ]);
285
- case 'tuic-v5':
286
- return joinString([
287
- `${config.name} = tuic-v5, ${config.server}, ${config.port}`,
288
- `password=${config.password}`,
289
- `uuid=${config.uuid}`,
290
- `alpn=${config.alpn}`,
291
- `skip-cert-verify=${config.skipCertVerify}`,
292
- `sni=${config.sni}`,
293
- ...shared
294
- ]);
295
- default:
296
- guard.never(config, 'type (clash encode)');
297
- }
298
- }
299
-
300
- var index$3 = {
301
- __proto__: null,
302
- decode: decode$1,
303
- encode: encode$1
304
- };
305
-
306
- function decode(config) {
307
- if (!('type' in config) || typeof config.type !== 'string') {
308
- throw new TypeError('Missing or invalid type field');
309
- }
310
- const raw = JSON.stringify(config);
311
- switch(config.type){
312
- case 'http':
313
- return {
314
- type: 'http',
315
- name: config.name,
316
- server: config.server,
317
- port: Number(config.port),
318
- username: config.username,
319
- password: config.password,
320
- raw
321
- };
322
- case 'ss':
323
- return {
324
- type: 'ss',
325
- name: config.name,
326
- server: config.server,
327
- port: Number(config.port),
328
- cipher: config.cipher,
329
- password: config.password,
330
- udp: config.udp || false,
331
- obfs: config.plugin === 'obfs' ? config['plugin-opts'].mode : undefined,
332
- raw
333
- };
334
- case 'socks5':
335
- return {
336
- type: 'socks5',
337
- name: config.name,
338
- server: config.server,
339
- port: Number(config.port),
340
- username: config.username,
341
- password: config.password,
342
- udp: config.udp || false,
343
- raw
344
- };
345
- case 'trojan':
346
- return {
347
- type: 'trojan',
348
- name: config.name,
349
- server: config.server,
350
- port: Number(config.port),
351
- password: config.password,
352
- sni: config.sni,
353
- skipCertVerify: config['skip-cert-verify'] || false,
354
- udp: config.udp || false,
355
- raw
356
- };
357
- case 'vmess':
358
- return {
359
- type: 'vmess',
360
- name: config.name,
361
- server: config.server,
362
- port: Number(config.port),
363
- username: config.uuid,
364
- vmessAead: config.alterId === 1 || config.alterId === '1',
365
- sni: config.servername,
366
- ws: config.network === 'ws',
367
- wsPath: config['ws-path'],
368
- wsHeaders: config['ws-headers'] ? Object.entries(config['ws-headers']).map(([key, value])=>`${key}:${value}`).join(', ') : undefined,
369
- tls: config.tls || false,
370
- udp: config.udp ?? true,
371
- raw,
372
- skipCertVerify: config['skip-cert-verify'] || false
373
- };
374
- default:
375
- throw new TypeError(`Unsupported type: ${config.type} (clash decode)`);
376
- }
377
- }
378
- function encode(config) {
379
- const shared = {
380
- tfo: config.tfo
381
- };
382
- switch(config.type){
383
- case 'ss':
384
- return {
385
- name: config.name,
386
- type: 'ss',
387
- server: config.server,
388
- port: config.port,
389
- cipher: config.cipher,
390
- password: config.password,
391
- udp: config.udp,
392
- ...config.obfs ? {
393
- plugin: 'obfs',
394
- 'plugin-opts': {
395
- mode: config.obfs,
396
- host: config.obfsHost,
397
- uri: config.obfsUri
398
- }
399
- } : {},
400
- ...shared
401
- };
402
- case 'trojan':
403
- return {
404
- name: config.name,
405
- type: 'trojan',
406
- server: config.server,
407
- port: config.port,
408
- password: config.password,
409
- sni: config.sni,
410
- 'skip-cert-verify': config.skipCertVerify,
411
- udp: config.udp,
412
- ...shared
413
- };
414
- case 'tuic':
415
- case 'tuic-v5':
416
- return {
417
- name: config.name,
418
- type: 'tuic',
419
- server: config.server,
420
- port: config.port,
421
- sni: config.sni,
422
- uuid: config.uuid,
423
- alpn: config.alpn.split(',').map((x)=>x.trim()),
424
- ...config.type === 'tuic' ? {
425
- token: config.token
426
- } : {
427
- password: config.password
428
- },
429
- udp: true,
430
- ...shared
431
- };
432
- case 'socks5':
433
- return {
434
- name: config.name,
435
- type: 'socks5',
436
- server: config.server,
437
- port: config.port,
438
- username: config.username,
439
- password: config.password,
440
- udp: config.udp,
441
- ...shared
442
- };
443
- case 'http':
444
- return {
445
- name: config.name,
446
- type: 'http',
447
- server: config.server,
448
- port: config.port,
449
- username: config.username,
450
- password: config.password,
451
- ...shared
452
- };
453
- case 'vmess':
454
- return {
455
- alterId: config.vmessAead ? 0 : undefined,
456
- tls: config.tls,
457
- udp: config.udp,
458
- uuid: config.username,
459
- name: config.name,
460
- servername: config.sni,
461
- 'ws-path': config.wsPath,
462
- server: config.server,
463
- 'ws-headers': config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined,
464
- cipher: 'auto',
465
- 'ws-opts': {
466
- path: config.wsPath,
467
- headers: config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined
468
- },
469
- type: 'vmess',
470
- port: config.port,
471
- network: config.ws ? 'ws' : 'tcp'
472
- };
473
- case 'hysteria2':
474
- return {
475
- name: config.name,
476
- type: 'hysteria2',
477
- server: config.server,
478
- port: config.port,
479
- ports: config.portHopping,
480
- password: config.password,
481
- down: config.downloadBandwidth + ' Mbps',
482
- 'skip-cert-verify': config.skipCertVerify
483
- };
484
- default:
485
- throw new TypeError(`Unsupported type: ${config.type} (clash encode)`);
486
- }
487
- }
488
- function parseStringToObject(input) {
489
- return input.split(',').reduce((acc, pair)=>{
490
- const [key, value] = pair.split(':');
491
- acc[key.trim()] = value.trim();
492
- return acc;
493
- }, {});
494
- }
495
-
496
- var index$2 = {
497
- __proto__: null,
498
- decode: decode,
499
- encode: encode
500
- };
501
-
502
- function decodeOne(sip002) {
503
- // ss://YWVzLTEyOC1nY206YzMxNWFhOGMtNGU1NC00MGRjLWJkYzctYzFjMjEwZjIxYTNi@ss1.meslink.xyz:10009#%F0%9F%87%AD%F0%9F%87%B0%20HK1%20HKT
504
- const [type, payload] = sip002.split('://');
505
- if (type !== 'ss') {
506
- throw new Error(`[ss.decodeOne] Unsupported type: ${type}`);
507
- }
508
- const [userInfo, server] = payload.split('@');
509
- let cipher, password;
510
- if (userInfo.includes(':')) {
511
- [cipher, password] = userInfo.split(':');
512
- } else {
513
- [cipher, password] = atob(userInfo).split(':');
514
- }
515
- const [serverName, _1] = server.split(':');
516
- const [_2, encodedName] = _1.split('#');
517
- const [port, pluginsStr] = _2.split('/');
518
- let plugin = null;
519
- if (pluginsStr) {
520
- try {
521
- plugin = new URLSearchParams(pluginsStr).get('plugin');
522
- } catch (e) {
523
- const err = new Error(`[ss.decodeOne] Invalid plugins: ${pluginsStr}`);
524
- err.cause = e;
525
- throw err;
526
- }
527
- }
528
- const pluginArgs = (plugin?.split(';') ?? []).reduce((acc, cur)=>{
529
- const [key, value] = cur.split('=');
530
- acc[key] = value;
531
- return acc;
532
- }, {});
533
- return {
534
- raw: sip002,
535
- type: 'ss',
536
- name: decodeURIComponent(encodedName),
537
- server: serverName,
538
- port: number(port),
539
- cipher,
540
- password,
541
- udp: true,
542
- obfs: 'obfs-local' in pluginArgs && 'obfs' in pluginArgs && (pluginArgs.obfs === 'http' || pluginArgs.obfs === 'tls') ? pluginArgs.obfs : undefined,
543
- obfsHost: 'obfs-host' in pluginArgs ? pluginArgs['obfs-host'] : undefined
544
- };
545
- }
546
- function decodeBase64Multiline(text) {
547
- return atob(text).replaceAll('\r\n', '\n').split('\n').filter(Boolean);
548
- }
549
- function decodeMultiline(text) {
550
- return decodeBase64Multiline(text).map((line)=>decodeOne(line));
551
- }
552
-
553
- var index$1 = {
554
- __proto__: null,
555
- decodeBase64Multiline: decodeBase64Multiline,
556
- decodeMultiline: decodeMultiline,
557
- decodeOne: decodeOne
558
- };
559
-
560
- function parse(line) {
561
- const url = new URL(line);
562
- // trojan://password@remote_host:remote_port
563
- const password = url.username;
564
- const server = url.hostname;
565
- const port = Number.parseInt(url.port, 10);
566
- if (Number.isNaN(port)) {
567
- throw new TypeError('invalid port: ' + url.port);
568
- }
569
- const name = decodeURIComponent(url.hash.slice(1));
570
- return {
571
- raw: line,
572
- name,
573
- type: 'trojan',
574
- server,
575
- port,
576
- password,
577
- udp: true,
578
- sni: url.searchParams.get('sni') ?? server,
579
- skipCertVerify: true
580
- };
581
- }
582
-
583
- var index = {
584
- __proto__: null,
585
- parse: parse
586
- };
587
-
588
- exports.clash = index$2;
589
- exports.ss = index$1;
590
- exports.surge = index$3;
591
- exports.trojan = index;
1
+ var e=require("foxts/guard");const r=e=>"true"===e,s=Number,t=e=>e.split(",").map(e=>e.trim());function p(e){let r=e.indexOf("=");return -1===r?["",""]:[e.slice(0,r).trim(),e.slice(r+1).trim()]}const o=new Set(["udp-relay","tfo","reuse","skip-cert-verify","tls","vmess-aead","ws"]),n=e=>o.has(e),a=new Set(["version","download-bandwidth","port-hopping-interval","udp-port"]),i=e=>a.has(e),u=new Set([]),d=e=>u.has(e),c=new Set(["username","password","sni","encrypt-method","psk","obfs","obfs-host","uuid","alpn","block-quic","ws-path","ws-headers","port-hopping","token"]),l=e=>c.has(e),w=Symbol("unsupported"),m=e=>e.filter(Boolean).join(", ");function f(e){return e.split(",").reduce((e,r)=>{let[s,t]=r.split(":");return e[s.trim()]=t.trim(),e},{})}function v(e){let r,t;let[p,o]=e.split("://");if("ss"!==p)throw Error(`[ss.decodeOne] Unsupported type: ${p}`);let[n,a]=o.split("@");n.includes(":")?[r,t]=n.split(":"):[r,t]=atob(n).split(":");let[i,u]=a.split(":"),[d,c]=u.split("#"),[l,w]=d.split("/"),m=null;if(w)try{m=new URLSearchParams(w).get("plugin")}catch(r){let e=Error(`[ss.decodeOne] Invalid plugins: ${w}`);throw e.cause=r,e}let f=(m?.split(";")??[]).reduce((e,r)=>{let[s,t]=r.split("=");return e[s]=t,e},{});return{raw:e,type:"ss",name:decodeURIComponent(c),server:i,port:s(l),cipher:r,password:t,udp:!0,obfs:"obfs-local"in f&&"obfs"in f&&("http"===f.obfs||"tls"===f.obfs)?f.obfs:void 0,obfsHost:"obfs-host"in f?f["obfs-host"]:void 0}}function y(e){return atob(e).replaceAll("\r\n","\n").split("\n").filter(Boolean)}exports.clash={__proto__:null,decode:function(e){if(!("type"in e)||"string"!=typeof e.type)throw TypeError("Missing or invalid type field");let r=JSON.stringify(e);switch(e.type){case"http":return{type:"http",name:e.name,server:e.server,port:Number(e.port),username:e.username,password:e.password,raw:r};case"ss":return{type:"ss",name:e.name,server:e.server,port:Number(e.port),cipher:e.cipher,password:e.password,udp:e.udp||!1,obfs:"obfs"===e.plugin?e["plugin-opts"].mode:void 0,raw:r};case"socks5":return{type:"socks5",name:e.name,server:e.server,port:Number(e.port),username:e.username,password:e.password,udp:e.udp||!1,raw:r};case"trojan":return{type:"trojan",name:e.name,server:e.server,port:Number(e.port),password:e.password,sni:e.sni,skipCertVerify:e["skip-cert-verify"]||!1,udp:e.udp||!1,raw:r};case"vmess":return{type:"vmess",name:e.name,server:e.server,port:Number(e.port),username:e.uuid,vmessAead:1===e.alterId||"1"===e.alterId,sni:e.servername,ws:"ws"===e.network,wsPath:e["ws-path"],wsHeaders:e["ws-headers"]?Object.entries(e["ws-headers"]).map(([e,r])=>`${e}:${r}`).join(", "):void 0,tls:e.tls||!1,udp:e.udp??!0,raw:r,skipCertVerify:e["skip-cert-verify"]||!1};default:throw TypeError(`Unsupported type: ${e.type} (clash decode)`)}},encode:function(e){let r={tfo:e.tfo};switch(e.type){case"ss":return{name:e.name,type:"ss",server:e.server,port:e.port,cipher:e.cipher,password:e.password,udp:e.udp,...e.obfs?{plugin:"obfs","plugin-opts":{mode:e.obfs,host:e.obfsHost,uri:e.obfsUri}}:{},...r};case"trojan":return{name:e.name,type:"trojan",server:e.server,port:e.port,password:e.password,sni:e.sni,"skip-cert-verify":e.skipCertVerify,udp:e.udp,...r};case"tuic":case"tuic-v5":return{name:e.name,type:"tuic",server:e.server,port:e.port,sni:e.sni,uuid:e.uuid,alpn:e.alpn.split(",").map(e=>e.trim()),..."tuic"===e.type?{token:e.token}:{password:e.password},"skip-cert-verify":e.skipCertVerify,udp:!0,...r};case"socks5":return{name:e.name,type:"socks5",server:e.server,port:e.port,username:e.username,password:e.password,udp:e.udp,...r};case"http":return{name:e.name,type:"http",server:e.server,port:e.port,username:e.username,password:e.password,...r};case"vmess":return{alterId:e.vmessAead?0:void 0,tls:e.tls,udp:e.udp,uuid:e.username,name:e.name,servername:e.sni,"ws-path":e.wsPath,server:e.server,"ws-headers":e.wsHeaders?f(e.wsHeaders):void 0,cipher:"auto","ws-opts":{path:e.wsPath,headers:e.wsHeaders?f(e.wsHeaders):void 0},type:"vmess",port:e.port,network:e.ws?"ws":"tcp"};case"hysteria2":return{name:e.name,type:"hysteria2",server:e.server,port:e.port,ports:e.portHopping,password:e.password,down:e.downloadBandwidth+" Mbps","skip-cert-verify":e.skipCertVerify};default:throw TypeError(`Unsupported type: ${e.type} (clash encode)`)}}},exports.ss={__proto__:null,decodeBase64Multiline:y,decodeMultiline:function(e){return y(e).map(e=>v(e))},decodeOne:v},exports.surge={__proto__:null,decode:function(e){let[o,a]=p(e),[u,c,m,...f]=t(a),v=s(m),y=Object.fromEntries(f.map(e=>{let[o,a]=p(e);return n(o)?[o,r(a)]:i(o)?[o,s(a)]:d(o)?[o,t(a)]:l(o)?'"'===a[0]&&a.endsWith('"')||"'"===a[0]&&a.endsWith("'")?[o,a.slice(1,-1)]:[o,a]:[o,w]})),h={raw:e,name:o,server:c,port:v,tfo:y.tfo,blockQuic:y["block-quic"]};switch(u){case"snell":return{type:"snell",psk:y.psk,version:y.version,reuse:y.reuse,...h};case"ss":return{type:"ss",cipher:y["encrypt-method"],password:y.password,udp:y["udp-relay"],obfs:y.obfs,obfsHost:y["obfs-host"],obfsUri:y["obfs-uri"],udpPort:y["udp-port"],...h};case"trojan":return{type:"trojan",password:y.password,sni:y.sni,skipCertVerify:y["skip-cert-verify"],udp:y["udp-relay"],...h};case"tuic":return{type:"tuic",sni:y.sni,uuid:y.uuid,alpn:y.alpn,token:y.token,skipCertVerify:y["skip-cert-verify"],...h};case"tuic-v5":return{type:"tuic-v5",uuid:y.uuid,alpn:y.alpn,password:y.password,sni:y.sni,skipCertVerify:y["skip-cert-verify"],...h};case"socks5":return{type:"socks5",username:f[0],password:f[1],udp:y["udp-relay"],...h};case"http":return{type:"http",username:f[0],password:f[1],...h};case"vmess":return{type:"vmess",username:y.username,tls:y.tls,vmessAead:y["vmess-aead"],ws:y.ws,wsPath:y["ws-path"],wsHeaders:y["ws-headers"],skipCertVerify:y["skip-cert-verify"],udp:y["udp-relay"],sni:y.sni,...h};case"hysteria2":return{type:"hysteria2",password:y.password,skipCertVerify:y["skip-cert-verify"],downloadBandwidth:y["download-bandwidth"],portHopping:y["port-hopping"],portHoppingInterval:y["port-hopping-interval"],...h};default:throw TypeError(`Unsupported type: ${u} (surge decode)`)}},encode:function(r){let s=[r.tfo&&"tfo=true",r.blockQuic&&`block-quic=${r.blockQuic}`];switch(r.type){case"snell":return m([`${r.name} = snell, ${r.server}, ${r.port}, psk=${r.psk}, version=${r.version}, reuse=${r.reuse}`,...s]);case"ss":return m([`${r.name} = ss, ${r.server}, ${r.port}, encrypt-method=${r.cipher}, password=${r.password}`,r.udp&&"udp-relay=true",r.udpPort&&`udp-port=${r.udpPort}`,r.obfs&&`obfs=${r.obfs}`,r.obfsHost&&`obfs-host=${r.obfsHost}`,r.obfsUri&&`obfs-uri=${r.obfsUri}`,...s]);case"trojan":return m([`${r.name} = trojan, ${r.server}, ${r.port}, password=${r.password}`,r.sni&&`sni=${r.sni}`,r.skipCertVerify&&"skip-cert-verify=true",...s,r.udp&&"udp-relay=true"]);case"tuic":return m([`${r.name} = tuic, ${r.server}, ${r.port}, sni=${r.sni}, uuid=${r.uuid}, alpn=${r.alpn}, token=${r.token}`,...s]);case"socks5":return m([`${r.name} = socks5, ${r.server}, ${r.port}, ${r.username}, ${r.password}`,r.udp&&"udp-relay=true",...s]);case"http":return m([`${r.name} = http, ${r.server}, ${r.port}, ${r.username}, ${r.password}`,...s]);case"vmess":return m([`${r.name} = vmess, ${r.server}, ${r.port}`,`username=${r.username}`,`tls=${r.tls}`,`vmess-aead=${r.vmessAead}`,"ws=true",r.wsPath&&`ws-path=${"/"===r.wsPath[0]?r.wsPath:`/${r.wsPath}`}`,r.wsHeaders&&`ws-headers=${r.wsHeaders}`,`skip-cert-verify=${r.skipCertVerify}`,`tfo=${r.tfo}`,`udp-relay=${r.udp}`]);case"hysteria2":return m([`${r.name} = hysteria2, ${r.server}, ${r.port}`,`password=${r.password}`,`download-bandwidth=${r.downloadBandwidth}`,r.portHopping&&`port-hopping="${r.portHopping}"`,r.portHoppingInterval&&`port-hopping-interval=${r.portHoppingInterval}`,`skip-cert-verify=${r.skipCertVerify}`,...s]);case"tuic-v5":return m([`${r.name} = tuic-v5, ${r.server}, ${r.port}`,`password=${r.password}`,`uuid=${r.uuid}`,`alpn=${r.alpn}`,`skip-cert-verify=${r.skipCertVerify}`,`sni=${r.sni}`,...s]);default:e.never(r,"type (clash encode)")}}},exports.trojan={__proto__:null,parse:function(e){let r=new URL(e),s=r.username,t=r.hostname,p=Number.parseInt(r.port,10);if(Number.isNaN(p))throw TypeError("invalid port: "+r.port);return{raw:e,name:decodeURIComponent(r.hash.slice(1)),type:"trojan",server:t,port:p,password:s,udp:!0,sni:r.searchParams.get("sni")??t,skipCertVerify:!0}}};
@@ -52,7 +52,7 @@ interface TrojanBasicConfig extends SharedConfigBase, TlsSharedConfig {
52
52
  /** udp-relay */
53
53
  udp: boolean;
54
54
  }
55
- interface TuicConfig extends SharedConfigBase {
55
+ interface TuicConfig extends SharedConfigBase, TlsSharedConfig {
56
56
  type: 'tuic';
57
57
  sni: string;
58
58
  uuid: string;
@@ -152,6 +152,7 @@ declare function encode(config: SupportedConfig): {
152
152
  down?: undefined;
153
153
  } | {
154
154
  tfo: boolean | undefined;
155
+ 'skip-cert-verify': boolean;
155
156
  udp: boolean;
156
157
  token: string;
157
158
  name: string;
@@ -172,9 +173,9 @@ declare function encode(config: SupportedConfig): {
172
173
  ports?: undefined;
173
174
  password?: undefined;
174
175
  down?: undefined;
175
- 'skip-cert-verify'?: undefined;
176
176
  } | {
177
177
  tfo: boolean | undefined;
178
+ 'skip-cert-verify': boolean;
178
179
  udp: boolean;
179
180
  password: string;
180
181
  name: string;
@@ -194,7 +195,6 @@ declare function encode(config: SupportedConfig): {
194
195
  network?: undefined;
195
196
  ports?: undefined;
196
197
  down?: undefined;
197
- 'skip-cert-verify'?: undefined;
198
198
  } | {
199
199
  tfo: boolean | undefined;
200
200
  name: string;
package/dist/es/index.mjs CHANGED
@@ -1,588 +1 @@
1
- import { never } from 'foxts/guard';
2
-
3
- const boolean = (text)=>text === 'true';
4
- const number = Number;
5
- const comma = (text)=>text.split(',').map((piece)=>piece.trim());
6
- function assign(text) {
7
- const signIndex = text.indexOf('=');
8
- return signIndex === -1 ? [
9
- '',
10
- ''
11
- ] : [
12
- text.slice(0, signIndex).trim(),
13
- text.slice(signIndex + 1).trim()
14
- ];
15
- }
16
-
17
- const boolKeys = new Set([
18
- 'udp-relay',
19
- 'tfo',
20
- 'reuse',
21
- 'skip-cert-verify',
22
- 'tls',
23
- 'vmess-aead',
24
- 'ws'
25
- ]);
26
- const isProxyBoolKey = (key)=>boolKeys.has(key);
27
- const numKeys = new Set([
28
- 'version',
29
- 'download-bandwidth',
30
- 'port-hopping-interval',
31
- 'udp-port'
32
- ]);
33
- const isProxyNumKey = (key)=>numKeys.has(key);
34
- const arrKeys = new Set([]);
35
- const isProxyArrKey = (key)=>arrKeys.has(key);
36
- const strKeys = new Set([
37
- 'username',
38
- 'password',
39
- 'sni',
40
- 'encrypt-method',
41
- 'psk',
42
- 'obfs',
43
- 'obfs-host',
44
- 'uuid',
45
- 'alpn',
46
- 'block-quic',
47
- 'ws-path',
48
- 'ws-headers',
49
- 'port-hopping',
50
- 'token'
51
- ]);
52
- const isProxyStrKey = (key)=>strKeys.has(key);
53
- const UNSUPPORTED_VALUE = Symbol('unsupported');
54
- function decode$1(raw) {
55
- const parsePart = (part)=>{
56
- const [key, value] = assign(part);
57
- if (isProxyBoolKey(key)) {
58
- return [
59
- key,
60
- boolean(value)
61
- ];
62
- }
63
- if (isProxyNumKey(key)) {
64
- return [
65
- key,
66
- number(value)
67
- ];
68
- }
69
- if (isProxyArrKey(key)) {
70
- return [
71
- key,
72
- comma(value)
73
- ];
74
- }
75
- if (isProxyStrKey(key)) {
76
- if (value[0] === '"' && value.endsWith('"') || value[0] === '\'' && value.endsWith('\'')) {
77
- return [
78
- key,
79
- value.slice(1, -1)
80
- ];
81
- }
82
- return [
83
- key,
84
- value
85
- ];
86
- }
87
- return [
88
- key,
89
- UNSUPPORTED_VALUE
90
- ];
91
- };
92
- const [name, parts] = assign(raw);
93
- const [type, server, mayPort, ...rest] = comma(parts);
94
- const port = number(mayPort);
95
- const restDetails = Object.fromEntries(rest.map(parsePart));
96
- const shared = {
97
- raw,
98
- name,
99
- server,
100
- port,
101
- tfo: restDetails.tfo,
102
- blockQuic: restDetails['block-quic']
103
- };
104
- switch(type){
105
- case 'snell':
106
- {
107
- return {
108
- type: 'snell',
109
- psk: restDetails.psk,
110
- version: restDetails.version,
111
- reuse: restDetails.reuse,
112
- ...shared
113
- };
114
- }
115
- case 'ss':
116
- {
117
- return {
118
- type: 'ss',
119
- cipher: restDetails['encrypt-method'],
120
- password: restDetails.password,
121
- udp: restDetails['udp-relay'],
122
- obfs: restDetails.obfs,
123
- obfsHost: restDetails['obfs-host'],
124
- obfsUri: restDetails['obfs-uri'],
125
- udpPort: restDetails['udp-port'],
126
- ...shared
127
- };
128
- }
129
- case 'trojan':
130
- {
131
- return {
132
- type: 'trojan',
133
- password: restDetails.password,
134
- sni: restDetails.sni,
135
- skipCertVerify: restDetails['skip-cert-verify'],
136
- udp: restDetails['udp-relay'],
137
- ...shared
138
- };
139
- }
140
- case 'tuic':
141
- {
142
- return {
143
- type: 'tuic',
144
- sni: restDetails.sni,
145
- uuid: restDetails.uuid,
146
- alpn: restDetails.alpn,
147
- token: restDetails.token,
148
- ...shared
149
- };
150
- }
151
- case 'tuic-v5':
152
- {
153
- return {
154
- type: 'tuic-v5',
155
- uuid: restDetails.uuid,
156
- alpn: restDetails.alpn,
157
- password: restDetails.password,
158
- sni: restDetails.sni,
159
- skipCertVerify: restDetails['skip-cert-verify'],
160
- ...shared
161
- };
162
- }
163
- case 'socks5':
164
- {
165
- return {
166
- type: 'socks5',
167
- username: rest[0],
168
- password: rest[1],
169
- udp: restDetails['udp-relay'],
170
- ...shared
171
- };
172
- }
173
- case 'http':
174
- {
175
- return {
176
- type: 'http',
177
- username: rest[0],
178
- password: rest[1],
179
- ...shared
180
- };
181
- }
182
- case 'vmess':
183
- {
184
- return {
185
- type: 'vmess',
186
- username: restDetails.username,
187
- tls: restDetails.tls,
188
- vmessAead: restDetails['vmess-aead'],
189
- ws: restDetails.ws,
190
- wsPath: restDetails['ws-path'],
191
- wsHeaders: restDetails['ws-headers'],
192
- skipCertVerify: restDetails['skip-cert-verify'],
193
- udp: restDetails['udp-relay'],
194
- sni: restDetails.sni,
195
- ...shared
196
- };
197
- }
198
- case 'hysteria2':
199
- return {
200
- type: 'hysteria2',
201
- password: restDetails.password,
202
- skipCertVerify: restDetails['skip-cert-verify'],
203
- downloadBandwidth: restDetails['download-bandwidth'],
204
- portHopping: restDetails['port-hopping'],
205
- portHoppingInterval: restDetails['port-hopping-interval'],
206
- ...shared
207
- };
208
- default:
209
- throw new TypeError(`Unsupported type: ${type} (surge decode)`);
210
- }
211
- // console.log({
212
- // name, type, server, port, restDetails
213
- // });
214
- }
215
- const joinString = (arr)=>arr.filter(Boolean).join(', ');
216
- function encode$1(config) {
217
- const shared = [
218
- config.tfo && 'tfo=true',
219
- config.blockQuic && `block-quic=${config.blockQuic}`
220
- ];
221
- switch(config.type){
222
- case 'snell':
223
- return joinString([
224
- `${config.name} = snell, ${config.server}, ${config.port}, psk=${config.psk}, version=${config.version}, reuse=${config.reuse}`,
225
- ...shared
226
- ]);
227
- case 'ss':
228
- return joinString([
229
- `${config.name} = ss, ${config.server}, ${config.port}, encrypt-method=${config.cipher}, password=${config.password}`,
230
- config.udp && 'udp-relay=true',
231
- config.udpPort && `udp-port=${config.udpPort}`,
232
- config.obfs && `obfs=${config.obfs}`,
233
- config.obfsHost && `obfs-host=${config.obfsHost}`,
234
- config.obfsUri && `obfs-uri=${config.obfsUri}`,
235
- ...shared
236
- ]);
237
- case 'trojan':
238
- return joinString([
239
- `${config.name} = trojan, ${config.server}, ${config.port}, password=${config.password}`,
240
- config.sni && `sni=${config.sni}`,
241
- config.skipCertVerify && 'skip-cert-verify=true',
242
- ...shared,
243
- config.udp && 'udp-relay=true'
244
- ]);
245
- case 'tuic':
246
- return joinString([
247
- `${config.name} = tuic, ${config.server}, ${config.port}, sni=${config.sni}, uuid=${config.uuid}, alpn=${config.alpn}, token=${config.token}`,
248
- ...shared
249
- ]);
250
- case 'socks5':
251
- return joinString([
252
- `${config.name} = socks5, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
253
- config.udp && 'udp-relay=true',
254
- ...shared
255
- ]);
256
- case 'http':
257
- return joinString([
258
- `${config.name} = http, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
259
- // no udp support for http
260
- ...shared
261
- ]);
262
- case 'vmess':
263
- return joinString([
264
- `${config.name} = vmess, ${config.server}, ${config.port}`,
265
- `username=${config.username}`,
266
- `tls=${config.tls}`,
267
- `vmess-aead=${config.vmessAead}`,
268
- 'ws=true',
269
- config.wsPath && `ws-path=${config.wsPath[0] === '/' ? config.wsPath : `/${config.wsPath}`}`,
270
- config.wsHeaders && `ws-headers=${config.wsHeaders}`,
271
- `skip-cert-verify=${config.skipCertVerify}`,
272
- `tfo=${config.tfo}`,
273
- `udp-relay=${config.udp}`
274
- ]);
275
- case 'hysteria2':
276
- return joinString([
277
- `${config.name} = hysteria2, ${config.server}, ${config.port}`,
278
- `password=${config.password}`,
279
- `download-bandwidth=${config.downloadBandwidth}`,
280
- config.portHopping && `port-hopping="${config.portHopping}"`,
281
- config.portHoppingInterval && `port-hopping-interval=${config.portHoppingInterval}`,
282
- `skip-cert-verify=${config.skipCertVerify}`,
283
- ...shared
284
- ]);
285
- case 'tuic-v5':
286
- return joinString([
287
- `${config.name} = tuic-v5, ${config.server}, ${config.port}`,
288
- `password=${config.password}`,
289
- `uuid=${config.uuid}`,
290
- `alpn=${config.alpn}`,
291
- `skip-cert-verify=${config.skipCertVerify}`,
292
- `sni=${config.sni}`,
293
- ...shared
294
- ]);
295
- default:
296
- never(config, 'type (clash encode)');
297
- }
298
- }
299
-
300
- var index$3 = {
301
- __proto__: null,
302
- decode: decode$1,
303
- encode: encode$1
304
- };
305
-
306
- function decode(config) {
307
- if (!('type' in config) || typeof config.type !== 'string') {
308
- throw new TypeError('Missing or invalid type field');
309
- }
310
- const raw = JSON.stringify(config);
311
- switch(config.type){
312
- case 'http':
313
- return {
314
- type: 'http',
315
- name: config.name,
316
- server: config.server,
317
- port: Number(config.port),
318
- username: config.username,
319
- password: config.password,
320
- raw
321
- };
322
- case 'ss':
323
- return {
324
- type: 'ss',
325
- name: config.name,
326
- server: config.server,
327
- port: Number(config.port),
328
- cipher: config.cipher,
329
- password: config.password,
330
- udp: config.udp || false,
331
- obfs: config.plugin === 'obfs' ? config['plugin-opts'].mode : undefined,
332
- raw
333
- };
334
- case 'socks5':
335
- return {
336
- type: 'socks5',
337
- name: config.name,
338
- server: config.server,
339
- port: Number(config.port),
340
- username: config.username,
341
- password: config.password,
342
- udp: config.udp || false,
343
- raw
344
- };
345
- case 'trojan':
346
- return {
347
- type: 'trojan',
348
- name: config.name,
349
- server: config.server,
350
- port: Number(config.port),
351
- password: config.password,
352
- sni: config.sni,
353
- skipCertVerify: config['skip-cert-verify'] || false,
354
- udp: config.udp || false,
355
- raw
356
- };
357
- case 'vmess':
358
- return {
359
- type: 'vmess',
360
- name: config.name,
361
- server: config.server,
362
- port: Number(config.port),
363
- username: config.uuid,
364
- vmessAead: config.alterId === 1 || config.alterId === '1',
365
- sni: config.servername,
366
- ws: config.network === 'ws',
367
- wsPath: config['ws-path'],
368
- wsHeaders: config['ws-headers'] ? Object.entries(config['ws-headers']).map(([key, value])=>`${key}:${value}`).join(', ') : undefined,
369
- tls: config.tls || false,
370
- udp: config.udp ?? true,
371
- raw,
372
- skipCertVerify: config['skip-cert-verify'] || false
373
- };
374
- default:
375
- throw new TypeError(`Unsupported type: ${config.type} (clash decode)`);
376
- }
377
- }
378
- function encode(config) {
379
- const shared = {
380
- tfo: config.tfo
381
- };
382
- switch(config.type){
383
- case 'ss':
384
- return {
385
- name: config.name,
386
- type: 'ss',
387
- server: config.server,
388
- port: config.port,
389
- cipher: config.cipher,
390
- password: config.password,
391
- udp: config.udp,
392
- ...config.obfs ? {
393
- plugin: 'obfs',
394
- 'plugin-opts': {
395
- mode: config.obfs,
396
- host: config.obfsHost,
397
- uri: config.obfsUri
398
- }
399
- } : {},
400
- ...shared
401
- };
402
- case 'trojan':
403
- return {
404
- name: config.name,
405
- type: 'trojan',
406
- server: config.server,
407
- port: config.port,
408
- password: config.password,
409
- sni: config.sni,
410
- 'skip-cert-verify': config.skipCertVerify,
411
- udp: config.udp,
412
- ...shared
413
- };
414
- case 'tuic':
415
- case 'tuic-v5':
416
- return {
417
- name: config.name,
418
- type: 'tuic',
419
- server: config.server,
420
- port: config.port,
421
- sni: config.sni,
422
- uuid: config.uuid,
423
- alpn: config.alpn.split(',').map((x)=>x.trim()),
424
- ...config.type === 'tuic' ? {
425
- token: config.token
426
- } : {
427
- password: config.password
428
- },
429
- udp: true,
430
- ...shared
431
- };
432
- case 'socks5':
433
- return {
434
- name: config.name,
435
- type: 'socks5',
436
- server: config.server,
437
- port: config.port,
438
- username: config.username,
439
- password: config.password,
440
- udp: config.udp,
441
- ...shared
442
- };
443
- case 'http':
444
- return {
445
- name: config.name,
446
- type: 'http',
447
- server: config.server,
448
- port: config.port,
449
- username: config.username,
450
- password: config.password,
451
- ...shared
452
- };
453
- case 'vmess':
454
- return {
455
- alterId: config.vmessAead ? 0 : undefined,
456
- tls: config.tls,
457
- udp: config.udp,
458
- uuid: config.username,
459
- name: config.name,
460
- servername: config.sni,
461
- 'ws-path': config.wsPath,
462
- server: config.server,
463
- 'ws-headers': config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined,
464
- cipher: 'auto',
465
- 'ws-opts': {
466
- path: config.wsPath,
467
- headers: config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined
468
- },
469
- type: 'vmess',
470
- port: config.port,
471
- network: config.ws ? 'ws' : 'tcp'
472
- };
473
- case 'hysteria2':
474
- return {
475
- name: config.name,
476
- type: 'hysteria2',
477
- server: config.server,
478
- port: config.port,
479
- ports: config.portHopping,
480
- password: config.password,
481
- down: config.downloadBandwidth + ' Mbps',
482
- 'skip-cert-verify': config.skipCertVerify
483
- };
484
- default:
485
- throw new TypeError(`Unsupported type: ${config.type} (clash encode)`);
486
- }
487
- }
488
- function parseStringToObject(input) {
489
- return input.split(',').reduce((acc, pair)=>{
490
- const [key, value] = pair.split(':');
491
- acc[key.trim()] = value.trim();
492
- return acc;
493
- }, {});
494
- }
495
-
496
- var index$2 = {
497
- __proto__: null,
498
- decode: decode,
499
- encode: encode
500
- };
501
-
502
- function decodeOne(sip002) {
503
- // ss://YWVzLTEyOC1nY206YzMxNWFhOGMtNGU1NC00MGRjLWJkYzctYzFjMjEwZjIxYTNi@ss1.meslink.xyz:10009#%F0%9F%87%AD%F0%9F%87%B0%20HK1%20HKT
504
- const [type, payload] = sip002.split('://');
505
- if (type !== 'ss') {
506
- throw new Error(`[ss.decodeOne] Unsupported type: ${type}`);
507
- }
508
- const [userInfo, server] = payload.split('@');
509
- let cipher, password;
510
- if (userInfo.includes(':')) {
511
- [cipher, password] = userInfo.split(':');
512
- } else {
513
- [cipher, password] = atob(userInfo).split(':');
514
- }
515
- const [serverName, _1] = server.split(':');
516
- const [_2, encodedName] = _1.split('#');
517
- const [port, pluginsStr] = _2.split('/');
518
- let plugin = null;
519
- if (pluginsStr) {
520
- try {
521
- plugin = new URLSearchParams(pluginsStr).get('plugin');
522
- } catch (e) {
523
- const err = new Error(`[ss.decodeOne] Invalid plugins: ${pluginsStr}`);
524
- err.cause = e;
525
- throw err;
526
- }
527
- }
528
- const pluginArgs = (plugin?.split(';') ?? []).reduce((acc, cur)=>{
529
- const [key, value] = cur.split('=');
530
- acc[key] = value;
531
- return acc;
532
- }, {});
533
- return {
534
- raw: sip002,
535
- type: 'ss',
536
- name: decodeURIComponent(encodedName),
537
- server: serverName,
538
- port: number(port),
539
- cipher,
540
- password,
541
- udp: true,
542
- obfs: 'obfs-local' in pluginArgs && 'obfs' in pluginArgs && (pluginArgs.obfs === 'http' || pluginArgs.obfs === 'tls') ? pluginArgs.obfs : undefined,
543
- obfsHost: 'obfs-host' in pluginArgs ? pluginArgs['obfs-host'] : undefined
544
- };
545
- }
546
- function decodeBase64Multiline(text) {
547
- return atob(text).replaceAll('\r\n', '\n').split('\n').filter(Boolean);
548
- }
549
- function decodeMultiline(text) {
550
- return decodeBase64Multiline(text).map((line)=>decodeOne(line));
551
- }
552
-
553
- var index$1 = {
554
- __proto__: null,
555
- decodeBase64Multiline: decodeBase64Multiline,
556
- decodeMultiline: decodeMultiline,
557
- decodeOne: decodeOne
558
- };
559
-
560
- function parse(line) {
561
- const url = new URL(line);
562
- // trojan://password@remote_host:remote_port
563
- const password = url.username;
564
- const server = url.hostname;
565
- const port = Number.parseInt(url.port, 10);
566
- if (Number.isNaN(port)) {
567
- throw new TypeError('invalid port: ' + url.port);
568
- }
569
- const name = decodeURIComponent(url.hash.slice(1));
570
- return {
571
- raw: line,
572
- name,
573
- type: 'trojan',
574
- server,
575
- port,
576
- password,
577
- udp: true,
578
- sni: url.searchParams.get('sni') ?? server,
579
- skipCertVerify: true
580
- };
581
- }
582
-
583
- var index = {
584
- __proto__: null,
585
- parse: parse
586
- };
587
-
588
- export { index$2 as clash, index$1 as ss, index$3 as surge, index as trojan };
1
+ import{never as e}from"foxts/guard";let r=e=>"true"===e,s=Number,t=e=>e.split(",").map(e=>e.trim());function p(e){let r=e.indexOf("=");return -1===r?["",""]:[e.slice(0,r).trim(),e.slice(r+1).trim()]}let o=new Set(["udp-relay","tfo","reuse","skip-cert-verify","tls","vmess-aead","ws"]),n=e=>o.has(e),a=new Set(["version","download-bandwidth","port-hopping-interval","udp-port"]),i=e=>a.has(e),u=new Set([]),d=e=>u.has(e),c=new Set(["username","password","sni","encrypt-method","psk","obfs","obfs-host","uuid","alpn","block-quic","ws-path","ws-headers","port-hopping","token"]),l=e=>c.has(e),w=Symbol("unsupported"),m=e=>e.filter(Boolean).join(", ");var f={__proto__:null,decode:function(e){let[o,a]=p(e),[u,c,m,...f]=t(a),v=s(m),y=Object.fromEntries(f.map(e=>{let[o,a]=p(e);return n(o)?[o,r(a)]:i(o)?[o,s(a)]:d(o)?[o,t(a)]:l(o)?'"'===a[0]&&a.endsWith('"')||"'"===a[0]&&a.endsWith("'")?[o,a.slice(1,-1)]:[o,a]:[o,w]})),h={raw:e,name:o,server:c,port:v,tfo:y.tfo,blockQuic:y["block-quic"]};switch(u){case"snell":return{type:"snell",psk:y.psk,version:y.version,reuse:y.reuse,...h};case"ss":return{type:"ss",cipher:y["encrypt-method"],password:y.password,udp:y["udp-relay"],obfs:y.obfs,obfsHost:y["obfs-host"],obfsUri:y["obfs-uri"],udpPort:y["udp-port"],...h};case"trojan":return{type:"trojan",password:y.password,sni:y.sni,skipCertVerify:y["skip-cert-verify"],udp:y["udp-relay"],...h};case"tuic":return{type:"tuic",sni:y.sni,uuid:y.uuid,alpn:y.alpn,token:y.token,skipCertVerify:y["skip-cert-verify"],...h};case"tuic-v5":return{type:"tuic-v5",uuid:y.uuid,alpn:y.alpn,password:y.password,sni:y.sni,skipCertVerify:y["skip-cert-verify"],...h};case"socks5":return{type:"socks5",username:f[0],password:f[1],udp:y["udp-relay"],...h};case"http":return{type:"http",username:f[0],password:f[1],...h};case"vmess":return{type:"vmess",username:y.username,tls:y.tls,vmessAead:y["vmess-aead"],ws:y.ws,wsPath:y["ws-path"],wsHeaders:y["ws-headers"],skipCertVerify:y["skip-cert-verify"],udp:y["udp-relay"],sni:y.sni,...h};case"hysteria2":return{type:"hysteria2",password:y.password,skipCertVerify:y["skip-cert-verify"],downloadBandwidth:y["download-bandwidth"],portHopping:y["port-hopping"],portHoppingInterval:y["port-hopping-interval"],...h};default:throw TypeError(`Unsupported type: ${u} (surge decode)`)}},encode:function(r){let s=[r.tfo&&"tfo=true",r.blockQuic&&`block-quic=${r.blockQuic}`];switch(r.type){case"snell":return m([`${r.name} = snell, ${r.server}, ${r.port}, psk=${r.psk}, version=${r.version}, reuse=${r.reuse}`,...s]);case"ss":return m([`${r.name} = ss, ${r.server}, ${r.port}, encrypt-method=${r.cipher}, password=${r.password}`,r.udp&&"udp-relay=true",r.udpPort&&`udp-port=${r.udpPort}`,r.obfs&&`obfs=${r.obfs}`,r.obfsHost&&`obfs-host=${r.obfsHost}`,r.obfsUri&&`obfs-uri=${r.obfsUri}`,...s]);case"trojan":return m([`${r.name} = trojan, ${r.server}, ${r.port}, password=${r.password}`,r.sni&&`sni=${r.sni}`,r.skipCertVerify&&"skip-cert-verify=true",...s,r.udp&&"udp-relay=true"]);case"tuic":return m([`${r.name} = tuic, ${r.server}, ${r.port}, sni=${r.sni}, uuid=${r.uuid}, alpn=${r.alpn}, token=${r.token}`,...s]);case"socks5":return m([`${r.name} = socks5, ${r.server}, ${r.port}, ${r.username}, ${r.password}`,r.udp&&"udp-relay=true",...s]);case"http":return m([`${r.name} = http, ${r.server}, ${r.port}, ${r.username}, ${r.password}`,...s]);case"vmess":return m([`${r.name} = vmess, ${r.server}, ${r.port}`,`username=${r.username}`,`tls=${r.tls}`,`vmess-aead=${r.vmessAead}`,"ws=true",r.wsPath&&`ws-path=${"/"===r.wsPath[0]?r.wsPath:`/${r.wsPath}`}`,r.wsHeaders&&`ws-headers=${r.wsHeaders}`,`skip-cert-verify=${r.skipCertVerify}`,`tfo=${r.tfo}`,`udp-relay=${r.udp}`]);case"hysteria2":return m([`${r.name} = hysteria2, ${r.server}, ${r.port}`,`password=${r.password}`,`download-bandwidth=${r.downloadBandwidth}`,r.portHopping&&`port-hopping="${r.portHopping}"`,r.portHoppingInterval&&`port-hopping-interval=${r.portHoppingInterval}`,`skip-cert-verify=${r.skipCertVerify}`,...s]);case"tuic-v5":return m([`${r.name} = tuic-v5, ${r.server}, ${r.port}`,`password=${r.password}`,`uuid=${r.uuid}`,`alpn=${r.alpn}`,`skip-cert-verify=${r.skipCertVerify}`,`sni=${r.sni}`,...s]);default:e(r,"type (clash encode)")}}};function v(e){return e.split(",").reduce((e,r)=>{let[s,t]=r.split(":");return e[s.trim()]=t.trim(),e},{})}var y={__proto__:null,decode:function(e){if(!("type"in e)||"string"!=typeof e.type)throw TypeError("Missing or invalid type field");let r=JSON.stringify(e);switch(e.type){case"http":return{type:"http",name:e.name,server:e.server,port:Number(e.port),username:e.username,password:e.password,raw:r};case"ss":return{type:"ss",name:e.name,server:e.server,port:Number(e.port),cipher:e.cipher,password:e.password,udp:e.udp||!1,obfs:"obfs"===e.plugin?e["plugin-opts"].mode:void 0,raw:r};case"socks5":return{type:"socks5",name:e.name,server:e.server,port:Number(e.port),username:e.username,password:e.password,udp:e.udp||!1,raw:r};case"trojan":return{type:"trojan",name:e.name,server:e.server,port:Number(e.port),password:e.password,sni:e.sni,skipCertVerify:e["skip-cert-verify"]||!1,udp:e.udp||!1,raw:r};case"vmess":return{type:"vmess",name:e.name,server:e.server,port:Number(e.port),username:e.uuid,vmessAead:1===e.alterId||"1"===e.alterId,sni:e.servername,ws:"ws"===e.network,wsPath:e["ws-path"],wsHeaders:e["ws-headers"]?Object.entries(e["ws-headers"]).map(([e,r])=>`${e}:${r}`).join(", "):void 0,tls:e.tls||!1,udp:e.udp??!0,raw:r,skipCertVerify:e["skip-cert-verify"]||!1};default:throw TypeError(`Unsupported type: ${e.type} (clash decode)`)}},encode:function(e){let r={tfo:e.tfo};switch(e.type){case"ss":return{name:e.name,type:"ss",server:e.server,port:e.port,cipher:e.cipher,password:e.password,udp:e.udp,...e.obfs?{plugin:"obfs","plugin-opts":{mode:e.obfs,host:e.obfsHost,uri:e.obfsUri}}:{},...r};case"trojan":return{name:e.name,type:"trojan",server:e.server,port:e.port,password:e.password,sni:e.sni,"skip-cert-verify":e.skipCertVerify,udp:e.udp,...r};case"tuic":case"tuic-v5":return{name:e.name,type:"tuic",server:e.server,port:e.port,sni:e.sni,uuid:e.uuid,alpn:e.alpn.split(",").map(e=>e.trim()),..."tuic"===e.type?{token:e.token}:{password:e.password},"skip-cert-verify":e.skipCertVerify,udp:!0,...r};case"socks5":return{name:e.name,type:"socks5",server:e.server,port:e.port,username:e.username,password:e.password,udp:e.udp,...r};case"http":return{name:e.name,type:"http",server:e.server,port:e.port,username:e.username,password:e.password,...r};case"vmess":return{alterId:e.vmessAead?0:void 0,tls:e.tls,udp:e.udp,uuid:e.username,name:e.name,servername:e.sni,"ws-path":e.wsPath,server:e.server,"ws-headers":e.wsHeaders?v(e.wsHeaders):void 0,cipher:"auto","ws-opts":{path:e.wsPath,headers:e.wsHeaders?v(e.wsHeaders):void 0},type:"vmess",port:e.port,network:e.ws?"ws":"tcp"};case"hysteria2":return{name:e.name,type:"hysteria2",server:e.server,port:e.port,ports:e.portHopping,password:e.password,down:e.downloadBandwidth+" Mbps","skip-cert-verify":e.skipCertVerify};default:throw TypeError(`Unsupported type: ${e.type} (clash encode)`)}}};function h(e){let r,t;let[p,o]=e.split("://");if("ss"!==p)throw Error(`[ss.decodeOne] Unsupported type: ${p}`);let[n,a]=o.split("@");n.includes(":")?[r,t]=n.split(":"):[r,t]=atob(n).split(":");let[i,u]=a.split(":"),[d,c]=u.split("#"),[l,w]=d.split("/"),m=null;if(w)try{m=new URLSearchParams(w).get("plugin")}catch(r){let e=Error(`[ss.decodeOne] Invalid plugins: ${w}`);throw e.cause=r,e}let f=(m?.split(";")??[]).reduce((e,r)=>{let[s,t]=r.split("=");return e[s]=t,e},{});return{raw:e,type:"ss",name:decodeURIComponent(c),server:i,port:s(l),cipher:r,password:t,udp:!0,obfs:"obfs-local"in f&&"obfs"in f&&("http"===f.obfs||"tls"===f.obfs)?f.obfs:void 0,obfsHost:"obfs-host"in f?f["obfs-host"]:void 0}}function $(e){return atob(e).replaceAll("\r\n","\n").split("\n").filter(Boolean)}var k={__proto__:null,decodeBase64Multiline:$,decodeMultiline:function(e){return $(e).map(e=>h(e))},decodeOne:h},b={__proto__:null,parse:function(e){let r=new URL(e),s=r.username,t=r.hostname,p=Number.parseInt(r.port,10);if(Number.isNaN(p))throw TypeError("invalid port: "+r.port);return{raw:e,name:decodeURIComponent(r.hash.slice(1)),type:"trojan",server:t,port:p,password:s,udp:!0,sni:r.searchParams.get("sni")??t,skipCertVerify:!0}}};export{y as clash,k as ss,f as surge,b as trojan};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodelistparser",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "Surge / Mihomo (Clash.Meta) nodelist / proxy provider parser and generator.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "scripts": {
55
55
  "lint": "eslint --format=sukka .",
56
- "build": "bunchee",
56
+ "build": "bunchee --minify --no-sourcemap",
57
57
  "test": "mocha --require @swc-node/register src/*.test.ts src/**/*.test.ts",
58
58
  "prerelease": "pnpm run lint && pnpm run build",
59
59
  "release": "bumpp -r --all --commit \"release: %s\" --tag \"%s\""