nodelistparser 0.0.0 → 0.1.1

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.
@@ -0,0 +1,512 @@
1
+ const boolean = (text)=>text === 'true';
2
+ const number = Number;
3
+ const comma = (text)=>text.split(',').map((piece)=>piece.trim());
4
+ function assign(text) {
5
+ const signIndex = text.indexOf('=');
6
+ return signIndex === -1 ? [
7
+ '',
8
+ ''
9
+ ] : [
10
+ text.slice(0, signIndex).trim(),
11
+ text.slice(signIndex + 1).trim()
12
+ ];
13
+ }
14
+
15
+ const boolKeys = new Set([
16
+ 'udp-relay',
17
+ 'tfo',
18
+ 'reuse',
19
+ 'skip-cert-verify',
20
+ 'tls',
21
+ 'vmess-aead',
22
+ 'ws'
23
+ ]);
24
+ const isProxyBoolKey = (key)=>boolKeys.has(key);
25
+ const numKeys = new Set([
26
+ 'version',
27
+ 'download-bandwidth',
28
+ 'port-hopping-interval'
29
+ ]);
30
+ const isProxyNumKey = (key)=>numKeys.has(key);
31
+ const arrKeys = new Set([]);
32
+ const isProxyArrKey = (key)=>arrKeys.has(key);
33
+ const strKeys = new Set([
34
+ 'username',
35
+ 'password',
36
+ 'sni',
37
+ 'encrypt-method',
38
+ 'psk',
39
+ 'obfs',
40
+ 'obfs-host',
41
+ 'uuid',
42
+ 'alpn',
43
+ 'block-quic',
44
+ 'ws-path',
45
+ 'ws-headers',
46
+ 'port-hopping'
47
+ ]);
48
+ const isProxyStrKey = (key)=>strKeys.has(key);
49
+ const UNSUPPORTED_VALUE = Symbol('unsupported');
50
+ function decode$1(raw) {
51
+ const parsePart = (part)=>{
52
+ const [key, value] = assign(part);
53
+ if (isProxyBoolKey(key)) {
54
+ return [
55
+ key,
56
+ boolean(value)
57
+ ];
58
+ }
59
+ if (isProxyNumKey(key)) {
60
+ return [
61
+ key,
62
+ number(value)
63
+ ];
64
+ }
65
+ if (isProxyArrKey(key)) {
66
+ return [
67
+ key,
68
+ comma(value)
69
+ ];
70
+ }
71
+ if (isProxyStrKey(key)) {
72
+ if (value[0] === '"' && value.endsWith('"') || value[0] === '\'' && value.endsWith('\'')) {
73
+ return [
74
+ key,
75
+ value.slice(1, -1)
76
+ ];
77
+ }
78
+ return [
79
+ key,
80
+ value
81
+ ];
82
+ }
83
+ return [
84
+ key,
85
+ UNSUPPORTED_VALUE
86
+ ];
87
+ };
88
+ const [name, parts] = assign(raw);
89
+ const [type, server, mayPort, ...rest] = comma(parts);
90
+ const port = number(mayPort);
91
+ const restDetails = Object.fromEntries(rest.map(parsePart));
92
+ const shared = {
93
+ raw,
94
+ name,
95
+ server,
96
+ port,
97
+ tfo: restDetails.tfo,
98
+ blockQuic: restDetails['block-quic']
99
+ };
100
+ switch(type){
101
+ case 'snell':
102
+ {
103
+ return {
104
+ type: 'snell',
105
+ psk: restDetails.psk,
106
+ version: restDetails.version,
107
+ reuse: restDetails.reuse,
108
+ ...shared
109
+ };
110
+ }
111
+ case 'ss':
112
+ {
113
+ return {
114
+ type: 'ss',
115
+ cipher: restDetails['encrypt-method'],
116
+ password: restDetails.password,
117
+ udp: restDetails['udp-relay'],
118
+ obfs: restDetails.obfs,
119
+ obfsHost: restDetails['obfs-host'],
120
+ obfsUri: restDetails['obfs-uri'],
121
+ ...shared
122
+ };
123
+ }
124
+ case 'trojan':
125
+ {
126
+ return {
127
+ type: 'trojan',
128
+ password: restDetails.password,
129
+ sni: restDetails.sni,
130
+ skipCertVerify: restDetails['skip-cert-verify'],
131
+ udp: restDetails['udp-relay'],
132
+ ...shared
133
+ };
134
+ }
135
+ case 'tuic':
136
+ {
137
+ return {
138
+ type: 'tuic',
139
+ sni: restDetails.sni,
140
+ uuid: restDetails.uuid,
141
+ alpn: restDetails.alpn,
142
+ password: restDetails.password,
143
+ version: restDetails.version,
144
+ ...shared
145
+ };
146
+ }
147
+ case 'socks5':
148
+ {
149
+ return {
150
+ type: 'socks5',
151
+ username: rest[0],
152
+ password: rest[1],
153
+ udp: restDetails['udp-relay'],
154
+ ...shared
155
+ };
156
+ }
157
+ case 'http':
158
+ {
159
+ return {
160
+ type: 'http',
161
+ username: rest[0],
162
+ password: rest[1],
163
+ ...shared
164
+ };
165
+ }
166
+ case 'vmess':
167
+ {
168
+ return {
169
+ type: 'vmess',
170
+ username: restDetails.username,
171
+ tls: restDetails.tls,
172
+ vmessAead: restDetails['vmess-aead'],
173
+ ws: restDetails.ws,
174
+ wsPath: restDetails['ws-path'],
175
+ wsHeaders: restDetails['ws-headers'],
176
+ skipCertVerify: restDetails['skip-cert-verify'],
177
+ udp: restDetails['udp-relay'],
178
+ sni: restDetails.sni,
179
+ ...shared
180
+ };
181
+ }
182
+ case 'hysteria2':
183
+ return {
184
+ type: 'hysteria2',
185
+ password: restDetails.password,
186
+ skipCertVerify: restDetails['skip-cert-verify'],
187
+ downloadBandwidth: restDetails['download-bandwidth'],
188
+ portHopping: restDetails['port-hopping'],
189
+ portHoppingInterval: restDetails['port-hopping-interval'],
190
+ ...shared
191
+ };
192
+ default:
193
+ throw new TypeError(`Unsupported type: ${type} (surge decode)`);
194
+ }
195
+ // console.log({
196
+ // name, type, server, port, restDetails
197
+ // });
198
+ }
199
+ function assertNever(value, msg) {
200
+ throw new TypeError(`Unsupported type: ${msg}`);
201
+ }
202
+ const joinString = (arr)=>arr.filter(Boolean).join(', ');
203
+ function encode$1(config) {
204
+ const shared = [
205
+ config.tfo && 'tfo=true',
206
+ config.blockQuic && `block-quic=${config.blockQuic}`
207
+ ];
208
+ switch(config.type){
209
+ case 'snell':
210
+ return joinString([
211
+ `${config.name} = snell, ${config.server}, ${config.port}, psk=${config.psk}, version=${config.version}, reuse=${config.reuse}`,
212
+ ...shared
213
+ ]);
214
+ case 'ss':
215
+ return joinString([
216
+ `${config.name} = ss, ${config.server}, ${config.port}, encrypt-method=${config.cipher}, password=${config.password}`,
217
+ config.udp && 'udp-relay=true',
218
+ config.obfs && `obfs=${config.obfs}`,
219
+ config.obfsHost && `obfs-host=${config.obfsHost}`,
220
+ config.obfsUri && `obfs-uri=${config.obfsUri}`,
221
+ ...shared
222
+ ]);
223
+ case 'trojan':
224
+ return joinString([
225
+ `${config.name} = trojan, ${config.server}, ${config.port}, password=${config.password}`,
226
+ config.sni && `sni=${config.sni}`,
227
+ config.skipCertVerify && 'skip-cert-verify=true',
228
+ ...shared,
229
+ config.udp && 'udp-relay=true'
230
+ ]);
231
+ case 'tuic':
232
+ return joinString([
233
+ `${config.name} = tuic, ${config.server}, ${config.port}, sni=${config.sni}, uuid=${config.uuid}, alpn=${config.alpn}, password=${config.password}, version=${config.version}`,
234
+ ...shared
235
+ ]);
236
+ case 'socks5':
237
+ return joinString([
238
+ `${config.name} = socks5, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
239
+ config.udp && 'udp-relay=true',
240
+ ...shared
241
+ ]);
242
+ case 'http':
243
+ return joinString([
244
+ `${config.name} = http, ${config.server}, ${config.port}, ${config.username}, ${config.password}`,
245
+ // no udp support for http
246
+ ...shared
247
+ ]);
248
+ case 'vmess':
249
+ return joinString([
250
+ `${config.name} = vmess, ${config.server}, ${config.port}`,
251
+ `username=${config.username}`,
252
+ `tls=${config.tls}`,
253
+ `vmess-aead=${config.vmessAead}`,
254
+ 'ws=true',
255
+ config.wsPath && `ws-path=${config.wsPath[0] === '/' ? config.wsPath : `/${config.wsPath}`}`,
256
+ config.wsHeaders && `ws-headers=${config.wsHeaders}`,
257
+ `skip-cert-verify=${config.skipCertVerify}`,
258
+ `tfo=${config.tfo}`,
259
+ `udp-relay=${config.udp}`
260
+ ]);
261
+ case 'hysteria2':
262
+ return joinString([
263
+ `${config.name} = hysteria2, ${config.server}, ${config.port}`,
264
+ `password=${config.password}`,
265
+ `download-bandwidth=${config.downloadBandwidth}`,
266
+ config.portHopping && `port-hopping="${config.portHopping}"`,
267
+ config.portHoppingInterval && `port-hopping-interval=${config.portHoppingInterval}`,
268
+ `skip-cert-verify=${config.skipCertVerify}`,
269
+ ...shared
270
+ ]);
271
+ default:
272
+ assertNever(config, `Unsupported type: ${config.type} (clash encode)`);
273
+ }
274
+ }
275
+
276
+ var index$2 = {
277
+ __proto__: null,
278
+ decode: decode$1,
279
+ encode: encode$1
280
+ };
281
+
282
+ function decode(config) {
283
+ if (!('type' in config) || typeof config.type !== 'string') {
284
+ throw new TypeError('Missing or invalid type field');
285
+ }
286
+ const raw = JSON.stringify(config);
287
+ switch(config.type){
288
+ case 'http':
289
+ return {
290
+ type: 'http',
291
+ name: config.name,
292
+ server: config.server,
293
+ port: Number(config.port),
294
+ username: config.username,
295
+ password: config.password,
296
+ raw
297
+ };
298
+ case 'ss':
299
+ return {
300
+ type: 'ss',
301
+ name: config.name,
302
+ server: config.server,
303
+ port: Number(config.port),
304
+ cipher: config.cipher,
305
+ password: config.password,
306
+ udp: config.udp || false,
307
+ obfs: config.plugin === 'obfs' ? config['plugin-opts'].mode : undefined,
308
+ raw
309
+ };
310
+ case 'socks5':
311
+ return {
312
+ type: 'socks5',
313
+ name: config.name,
314
+ server: config.server,
315
+ port: Number(config.port),
316
+ username: config.username,
317
+ password: config.password,
318
+ udp: config.udp || false,
319
+ raw
320
+ };
321
+ case 'trojan':
322
+ return {
323
+ type: 'trojan',
324
+ name: config.name,
325
+ server: config.server,
326
+ port: Number(config.port),
327
+ password: config.password,
328
+ sni: config.sni,
329
+ skipCertVerify: config['skip-cert-verify'] || false,
330
+ udp: config.udp || false,
331
+ raw
332
+ };
333
+ case 'vmess':
334
+ return {
335
+ type: 'vmess',
336
+ name: config.name,
337
+ server: config.server,
338
+ port: Number(config.port),
339
+ username: config.uuid,
340
+ vmessAead: config.alterId === 1 || config.alterId === '1',
341
+ sni: config.servername,
342
+ ws: config.network === 'ws',
343
+ wsPath: config['ws-path'],
344
+ wsHeaders: config['ws-headers'] ? Object.entries(config['ws-headers']).map(([key, value])=>`${key}:${value}`).join(', ') : undefined,
345
+ tls: config.tls || false,
346
+ udp: config.udp ?? true,
347
+ raw,
348
+ skipCertVerify: config['skip-cert-verify'] || false
349
+ };
350
+ default:
351
+ throw new TypeError(`Unsupported type: ${config.type} (clash decode)`);
352
+ }
353
+ }
354
+ function encode(config) {
355
+ const shared = {
356
+ tfo: config.tfo
357
+ };
358
+ switch(config.type){
359
+ case 'ss':
360
+ return {
361
+ name: config.name,
362
+ type: 'ss',
363
+ server: config.server,
364
+ port: config.port,
365
+ cipher: config.cipher,
366
+ password: config.password,
367
+ udp: config.udp,
368
+ ...config.obfs ? {
369
+ plugin: 'obfs',
370
+ 'plugin-opts': {
371
+ mode: config.obfs,
372
+ host: config.obfsHost,
373
+ uri: config.obfsUri
374
+ }
375
+ } : {},
376
+ ...shared
377
+ };
378
+ case 'trojan':
379
+ return {
380
+ name: config.name,
381
+ type: 'trojan',
382
+ server: config.server,
383
+ port: config.port,
384
+ password: config.password,
385
+ sni: config.sni,
386
+ 'skip-cert-verify': config.skipCertVerify,
387
+ udp: config.udp,
388
+ ...shared
389
+ };
390
+ case 'tuic':
391
+ return {
392
+ name: config.name,
393
+ type: 'tuic',
394
+ server: config.server,
395
+ port: config.port,
396
+ sni: config.sni,
397
+ uuid: config.uuid,
398
+ alpn: config.alpn.split(',').map((x)=>x.trim()),
399
+ token: config.password,
400
+ version: config.version,
401
+ udp: true,
402
+ ...shared
403
+ };
404
+ case 'socks5':
405
+ return {
406
+ name: config.name,
407
+ type: 'socks5',
408
+ server: config.server,
409
+ port: config.port,
410
+ username: config.username,
411
+ password: config.password,
412
+ udp: config.udp,
413
+ ...shared
414
+ };
415
+ case 'http':
416
+ return {
417
+ name: config.name,
418
+ type: 'http',
419
+ server: config.server,
420
+ port: config.port,
421
+ username: config.username,
422
+ password: config.password,
423
+ ...shared
424
+ };
425
+ case 'vmess':
426
+ return {
427
+ alterId: config.vmessAead ? 0 : undefined,
428
+ tls: config.tls,
429
+ udp: config.udp,
430
+ uuid: config.username,
431
+ name: config.name,
432
+ servername: config.sni,
433
+ 'ws-path': config.wsPath,
434
+ server: config.server,
435
+ 'ws-headers': config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined,
436
+ cipher: 'auto',
437
+ 'ws-opts': {
438
+ path: config.wsPath,
439
+ headers: config.wsHeaders ? parseStringToObject(config.wsHeaders) : undefined
440
+ },
441
+ type: 'vmess',
442
+ port: config.port,
443
+ network: config.ws ? 'ws' : 'tcp'
444
+ };
445
+ case 'hysteria2':
446
+ return {
447
+ name: config.name,
448
+ type: 'hysteria2',
449
+ server: config.server,
450
+ port: config.port,
451
+ ports: config.portHopping,
452
+ password: config.password,
453
+ down: config.downloadBandwidth + ' Mbps',
454
+ 'skip-cert-verify': config.skipCertVerify
455
+ };
456
+ default:
457
+ throw new TypeError(`Unsupported type: ${config.type} (clash encode)`);
458
+ }
459
+ }
460
+ function parseStringToObject(input) {
461
+ return input.split(',').reduce((acc, pair)=>{
462
+ const [key, value] = pair.split(':');
463
+ acc[key.trim()] = value.trim();
464
+ return acc;
465
+ }, {});
466
+ }
467
+
468
+ var index$1 = {
469
+ __proto__: null,
470
+ decode: decode,
471
+ encode: encode
472
+ };
473
+
474
+ function decodeOne(sip002) {
475
+ // ss://YWVzLTEyOC1nY206YzMxNWFhOGMtNGU1NC00MGRjLWJkYzctYzFjMjEwZjIxYTNi@ss1.meslink.xyz:10009#%F0%9F%87%AD%F0%9F%87%B0%20HK1%20HKT
476
+ const [_type, payload] = sip002.split('://');
477
+ const [userInfo, server] = payload.split('@');
478
+ let cipher, password;
479
+ if (userInfo.includes(':')) {
480
+ [cipher, password] = userInfo.split(':');
481
+ } else {
482
+ [cipher, password] = atob(userInfo).split(':');
483
+ }
484
+ const [serverName, _1] = server.split(':');
485
+ const [_2, encodedName] = _1.split('#');
486
+ const [port, _plugins] = _2.split('/');
487
+ // TODO: implement plugin parsing
488
+ return {
489
+ raw: sip002,
490
+ type: 'ss',
491
+ name: decodeURIComponent(encodedName),
492
+ server: serverName,
493
+ port: number(port),
494
+ cipher,
495
+ password,
496
+ udp: true
497
+ };
498
+ }
499
+ function decodeMultiline(text) {
500
+ const lines = atob(text).replaceAll('\r\n', '\n').split('\n');
501
+ return lines.filter(Boolean).map((line)=>decodeOne(line));
502
+ }
503
+
504
+ var index = {
505
+ __proto__: null,
506
+ decodeMultiline: decodeMultiline,
507
+ decodeOne: decodeOne
508
+ };
509
+
510
+ exports.clash = index$1;
511
+ exports.ss = index;
512
+ exports.surge = index$2;