nodelistparser 0.0.0 → 0.1.2

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