Compare commits

...

50 Commits

Author SHA1 Message Date
xream
6c54518e84 chore: 日志
Some checks failed
build / build (push) Has been cancelled
2025-03-18 13:34:50 +08:00
Aritro37
dd92a26e6c Perf: 提前加载 .env;后端复用前端 Path
Some checks are pending
build / build (push) Waiting to run
2025-03-17 22:01:38 +08:00
xream
bb5c9d43d0 Merge pull request #430 from Aritro37/master
feat: 支持通过.env配置环境变量,后端支持设置前置路由
2025-03-17 17:07:37 +08:00
Aritro37
e54ac92357 feat: 支持通过.env配置环境变量,后端支持设置前置路由 2025-03-17 17:04:24 +08:00
xream
507e37021c feat: 增加更多的同步配置日志
Some checks are pending
build / build (push) Waiting to run
2025-03-16 15:48:47 +08:00
xream
a70dc7b913 feat: undici 配置重定向
Some checks are pending
build / build (push) Waiting to run
2025-03-15 22:50:47 +08:00
xream
fc56df7bfd fix: 处理 YAML short-idnull 的情况
Some checks are pending
build / build (push) Waiting to run
2025-03-15 16:29:19 +08:00
xream
1281df59f3 feat: 增强 VMess URI 解析兼容性; 修改导出文件名格式
Some checks failed
build / build (push) Has been cancelled
2025-03-13 20:19:45 +08:00
xream
1faa3fb793 fix: 修复 VMess URI IPv6 格式
Some checks are pending
build / build (push) Waiting to run
2025-03-13 19:26:53 +08:00
xream
47307716b2 feat: url 支持 credentials; 修改导出文件名格式 2025-03-13 17:42:48 +08:00
xream
312caa6880 feat: patch http-proxy; 使用 undici 替代 request 2025-03-13 13:02:19 +08:00
xream
15a51e0dd0 fix: 修复文件预览未使用代理策略的问题 2025-03-12 19:44:54 +08:00
xream
8116c78dda feat: 升级 http-proxy-middleware 2025-03-12 15:30:14 +08:00
xream
6a026a3d07 feat: mihomo hysteria2 兼容 obfs_password 字段
Some checks failed
build / build (push) Has been cancelled
2025-03-10 23:06:33 +08:00
xream
cef931fa5d feat: Hysteria2 URI 输入输出支持 hop-interval 和 keepalive 参数, 为保证兼容性, 输出时多端口暂时保持使用 mport 参数 2025-03-10 19:52:15 +08:00
xream
29525b3e22 feat: sing-box hop_interval 和 server_ports 不需要 includeUnsupportedProxy 2025-03-10 19:36:41 +08:00
xream
8f701570e4 feat: Stash 使用 includeUnsupportedProxy 参数开启 XTLS-uTLS-Vision-REALITY(版本>=2.8.0 时自动开启)
Some checks failed
build / build (push) Has been cancelled
2025-03-07 14:09:56 +08:00
xream
3f8269e835 feat: Node.js 环境支持自定义 JSON Body limit, 例: SUB_STORE_BODY_JSON_LIMIT=10mb 2025-03-07 13:59:22 +08:00
xream
465b62218a feat: 验证 mihomo ss cipher
Some checks failed
build / build (push) Has been cancelled
2025-03-05 15:25:16 +08:00
xream
d255390d48 fix: 修复 Surge shadow-tls-password 引号解析
Some checks are pending
build / build (push) Waiting to run
2025-03-04 22:36:47 +08:00
xream
72c7f4333a feat: SurgeMac mihomo 配置中支持自定义 DNS 2025-03-04 20:11:22 +08:00
xream
f35837ff9f feat: 支持 AnyTLS URI
Some checks are pending
build / build (push) Waiting to run
2025-03-03 20:52:31 +08:00
xream
c2c39c5de6 fix: 修复 Egern 输出
Some checks are pending
build / build (push) Waiting to run
2025-03-03 10:42:44 +08:00
xream
87a4b14ae2 feat(wip): 本地脚本支持传入参数 2025-03-02 19:02:15 +08:00
xream
ff1dacda87 区域过滤和协议过滤支持保留模式和过滤模式(后端需 >= 2.17.0, 前端需 >= 2.15.0)
Some checks are pending
build / build (push) Waiting to run
2025-03-02 11:06:33 +08:00
xream
9426f128c4 feat: Surge 输出会判断 HTTP 是否 headers 字段
Some checks are pending
build / build (push) Waiting to run
2025-03-01 21:43:14 +08:00
xream
ebc7173c95 feat: 文件类型为 mihomo 配置时, 来源可以为无
Some checks are pending
build / build (push) Waiting to run
2025-03-01 08:45:17 +08:00
xream
dd4e0cef68 feat: 扩展 scriptResourceCache 缓存, 详见 demo.js
Some checks are pending
build / build (push) Waiting to run
2025-02-28 15:54:04 +08:00
xream
b1618c3803 feat: 支持使用环境变量 SUB_STORE_PRODUCE_CRON 在后台定时处理订阅, 格式为 0 */2 * * *,sub,a;0 */3 * * *,col,b 2025-02-28 14:07:35 +08:00
xream
1b4c046b75 fix: mihomo 覆写可以多次使用
Some checks are pending
build / build (push) Waiting to run
2025-02-27 23:37:39 +08:00
xream
41034ceb46 feat: 规范化 subscription-userinfo 2025-02-27 23:23:32 +08:00
xream
6efb19c856 feat: geo 更新 2025-02-27 17:27:15 +08:00
xream
2cd30dfe68 feat: 内容无变化时 不进行上传; 增加 gist 数量日志
Some checks are pending
build / build (push) Waiting to run
2025-02-26 18:50:11 +08:00
xream
d53947d820 feat: sing-box 支持 anytls
Some checks failed
build / build (push) Has been cancelled
2025-02-23 09:48:09 +08:00
xream
7e75031e92 fix: 修复 short-id 正则
Some checks are pending
build / build (push) Waiting to run
2025-02-22 14:25:06 +08:00
xream
4a07c02dc1 feat: 支持 Shadowrocket Shadowsocks 输入中的 Shadow TLS 参数
Some checks failed
build / build (push) Has been cancelled
2025-02-21 01:44:34 +08:00
xream
95d6688539 fix: 修复 Shadowrocket 输出的 Shadow TLS
Some checks are pending
build / build (push) Waiting to run
2025-02-21 00:50:38 +08:00
xream
a23e2ffcd6 fix: uuid 只辅助判断, 不直接过滤 2025-02-20 22:52:35 +08:00
xream
fda1252d0e fix: 修复 Egern http 传输层 2025-02-20 22:24:39 +08:00
xream
62c5c2e15b fix: 修复 Loon ip-mode
Some checks are pending
build / build (push) Waiting to run
2025-02-19 17:15:31 +08:00
xream
ffabcc9391 feat: 支持 anytls 协议
Some checks are pending
build / build (push) Waiting to run
2025-02-19 17:01:40 +08:00
xream
0825f15d04 feat: Egern 支持 Shadow TLS
Some checks failed
build / build (push) Has been cancelled
2025-02-18 15:07:24 +08:00
xream
fbf6b5ce6e fix: UUID
Some checks failed
build / build (push) Has been cancelled
2025-02-16 05:05:33 +08:00
xream
3eb0816c88 fix: 修复 TUIC URI
Some checks are pending
build / build (push) Waiting to run
2025-02-15 20:47:34 +08:00
xream
8fc755ff02 fix: 文件类型为 mihomo 配置时, 不应处理本地或远程内容字段 2025-02-15 20:32:29 +08:00
xream
6d3d6fa1b3 feat: 仅匹配 UUIDv4 2025-02-15 19:58:34 +08:00
xream
4ef4431c2c feat: 兼容更多 TUIC URI 字段
Some checks are pending
build / build (push) Waiting to run
2025-02-14 23:27:01 +08:00
xream
5058662651 feat: 下载文件名增加前后缀
Some checks are pending
build / build (push) Waiting to run
2025-02-14 15:39:13 +08:00
xream
f9d120bac3 feat: 兼容 v2rayN 非标 TUIC URI
Some checks failed
build / build (push) Has been cancelled
2025-02-13 20:26:59 +08:00
xream
72a445ae33 doc: README 2025-02-12 22:39:18 +08:00
39 changed files with 1029 additions and 549 deletions

View File

@@ -32,14 +32,14 @@ Core functionalities:
example: `socks5+tls://user:pass@ip:port#name`
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
- [x] Clash Proxies YAML
- [x] Clash Proxy JSON(single line)
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
- [x] Surge (Direct, SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru)
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.16.39",
"version": "2.18.7",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
"main": "src/main.js",
"scripts": {
@@ -17,25 +17,31 @@
},
"author": "Peng-YM",
"license": "GPL-3.0",
"pnpm": {
"patchedDependencies": {
"http-proxy@1.18.1": "patches/http-proxy@1.18.1.patch"
}
},
"dependencies": {
"@maxmind/geoip2-node": "^5.0.0",
"automerge": "1.0.1-preview.7",
"body-parser": "^1.19.0",
"buffer": "^6.0.3",
"dotenv": "^16.4.7",
"connect-history-api-fallback": "^2.0.0",
"cron": "^3.1.6",
"dns-packet": "^5.6.1",
"express": "^4.17.1",
"http-proxy-middleware": "^2.0.6",
"http-proxy-middleware": "^3.0.3",
"ip-address": "^9.0.5",
"js-base64": "^3.7.2",
"jsrsasign": "^11.1.0",
"lodash": "^4.17.21",
"ms": "^2.1.3",
"nanoid": "^3.3.3",
"request": "^2.88.2",
"semver": "^7.6.3",
"static-js-yaml": "^1.0.0"
"static-js-yaml": "^1.0.0",
"undici": "^7.4.0"
},
"devDependencies": {
"@babel/core": "^7.18.0",

View File

@@ -0,0 +1,46 @@
diff --git a/lib/http-proxy/common.js b/lib/http-proxy/common.js
index 6513e81d80d5250ea249ea833f819ece67897c7e..486d4c896d65a3bb7cf63307af68facb3ddb886b 100644
--- a/lib/http-proxy/common.js
+++ b/lib/http-proxy/common.js
@@ -1,6 +1,5 @@
var common = exports,
url = require('url'),
- extend = require('util')._extend,
required = require('requires-port');
var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i,
@@ -40,10 +39,10 @@ common.setupOutgoing = function(outgoing, options, req, forward) {
);
outgoing.method = options.method || req.method;
- outgoing.headers = extend({}, req.headers);
+ outgoing.headers = Object.assign({}, req.headers);
if (options.headers){
- extend(outgoing.headers, options.headers);
+ Object.assign(outgoing.headers, options.headers);
}
if (options.auth) {
diff --git a/lib/http-proxy/index.js b/lib/http-proxy/index.js
index 977a4b3622b9eaac27689f06347ea4c5173a96cd..88b2d0fcfa03c3aafa47c7e6d38e64412c45a7cc 100644
--- a/lib/http-proxy/index.js
+++ b/lib/http-proxy/index.js
@@ -1,5 +1,4 @@
var httpProxy = module.exports,
- extend = require('util')._extend,
parse_url = require('url').parse,
EE3 = require('eventemitter3'),
http = require('http'),
@@ -47,9 +46,9 @@ function createRightProxy(type) {
args[cntr] !== res
) {
//Copy global options
- requestOptions = extend({}, options);
+ requestOptions = Object.assign({}, options);
//Overwrite with request options
- extend(requestOptions, args[cntr]);
+ Object.assign(requestOptions, args[cntr]);
cntr--;
}

289
backend/pnpm-lock.yaml generated
View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
http-proxy@1.18.1:
hash: 8071c23044f455271f4d4074ae4c7b81beec17a03aefd158d5f4edd4ef751c11
path: patches/http-proxy@1.18.1.patch
importers:
.:
@@ -33,8 +38,8 @@ importers:
specifier: ^4.17.1
version: 4.21.2
http-proxy-middleware:
specifier: ^2.0.6
version: 2.0.7
specifier: ^3.0.3
version: 3.0.3
ip-address:
specifier: ^9.0.5
version: 9.0.5
@@ -53,15 +58,15 @@ importers:
nanoid:
specifier: ^3.3.3
version: 3.3.8
request:
specifier: ^2.88.2
version: 2.88.2
semver:
specifier: ^7.6.3
version: 7.6.3
static-js-yaml:
specifier: ^1.0.0
version: 1.0.0
undici:
specifier: ^7.4.0
version: 7.4.0
devDependencies:
'@babel/core':
specifier: ^7.18.0
@@ -1072,9 +1077,6 @@ packages:
asn1.js@4.10.1:
resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==}
asn1@0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
assert-plus@1.0.0:
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
engines: {node: '>=0.8'}
@@ -1100,9 +1102,6 @@ packages:
resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==}
engines: {node: '>= 0.10'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'}
@@ -1115,12 +1114,6 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
aws-sign2@0.7.0:
resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
aws4@1.13.2:
resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
axios@0.21.4:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
@@ -1168,9 +1161,6 @@ packages:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
engines: {node: '>=0.10.0'}
bcrypt-pbkdf@1.0.2:
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
binary-extensions@1.13.1:
resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
engines: {node: '>=0.10.0'}
@@ -1327,9 +1317,6 @@ packages:
caniuse-lite@1.0.30001692:
resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==}
caseless@0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
chai@4.5.0:
resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
engines: {node: '>=4'}
@@ -1410,10 +1397,6 @@ packages:
combine-source-map@0.8.0:
resolution: {integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -1535,10 +1518,6 @@ packages:
dash-ast@2.0.1:
resolution: {integrity: sha512-5TXltWJGc+RdnabUGzhRae1TRq6m4gr+3K2wQX0is5/F2yS6MJXJvLyI3ErAnsAXuJoGqvfVD5icRgim07DrxQ==}
dashdash@1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'}
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@@ -1633,10 +1612,6 @@ packages:
defined@1.0.1:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
@@ -1702,9 +1677,6 @@ packages:
each-props@1.3.2:
resolution: {integrity: sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==}
ecc-jsbn@0.1.2:
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -2074,16 +2046,9 @@ packages:
resolution: {integrity: sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==}
engines: {node: '>=0.10.0'}
forever-agent@0.6.1:
resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
fork-stream@0.0.4:
resolution: {integrity: sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==}
form-data@2.3.3:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -2167,9 +2132,6 @@ packages:
resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
engines: {node: '>=0.10.0'}
getpass@0.1.7:
resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==}
glob-parent@3.1.0:
resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==}
@@ -2273,15 +2235,6 @@ packages:
resolution: {integrity: sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==}
engines: {node: '>= 0.10'}
har-schema@2.0.0:
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
engines: {node: '>=4'}
har-validator@5.1.5:
resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}
engines: {node: '>=6'}
deprecated: this library is no longer supported
has-ansi@2.0.0:
resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
engines: {node: '>=0.10.0'}
@@ -2366,23 +2319,14 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
http-proxy-middleware@2.0.7:
resolution: {integrity: sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/express': ^4.17.13
peerDependenciesMeta:
'@types/express':
optional: true
http-proxy-middleware@3.0.3:
resolution: {integrity: sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
http-proxy@1.18.1:
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
engines: {node: '>=8.0.0'}
http-signature@1.2.0:
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
engines: {node: '>=0.8', npm: '>=1.3.7'}
https-browserify@1.0.0:
resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==}
@@ -2597,10 +2541,6 @@ packages:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
is-plain-obj@3.0.0:
resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
engines: {node: '>=10'}
is-plain-object@2.0.4:
resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
engines: {node: '>=0.10.0'}
@@ -2637,9 +2577,6 @@ packages:
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
engines: {node: '>= 0.4'}
is-typedarray@1.0.0:
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
is-unc-path@1.0.0:
resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==}
engines: {node: '>=0.10.0'}
@@ -2691,9 +2628,6 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
isstream@0.1.2:
resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
@@ -2708,9 +2642,6 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
jsbn@0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
jsbn@1.1.0:
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
@@ -2736,9 +2667,6 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -2748,10 +2676,6 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
jsprim@1.4.2:
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
engines: {node: '>=0.6.0'}
jsprim@2.0.2:
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==}
engines: {'0': node >=0.6.0}
@@ -3065,9 +2989,6 @@ packages:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -3272,9 +3193,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -3360,9 +3278,6 @@ packages:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
@@ -3390,10 +3305,6 @@ packages:
resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==}
engines: {node: '>=0.6'}
qs@6.5.3:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
engines: {node: '>=0.6'}
querystring-es3@0.2.1:
resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
engines: {node: '>=0.4.x'}
@@ -3524,11 +3435,6 @@ packages:
resolution: {integrity: sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==}
engines: {node: '>= 0.10'}
request@2.88.2:
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
engines: {node: '>= 6'}
deprecated: request has been deprecated, see https://github.com/request/request/issues/3142
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -3790,11 +3696,6 @@ packages:
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
sshpk@1.18.0:
resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
engines: {node: '>=0.10.0'}
hasBin: true
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
@@ -4010,22 +3911,12 @@ packages:
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
hasBin: true
tough-cookie@2.5.0:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
transform-ast@2.4.4:
resolution: {integrity: sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==}
tty-browserify@0.0.1:
resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
tweetnacl@0.14.5:
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
type-check@0.3.2:
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
engines: {node: '>= 0.8.0'}
@@ -4112,6 +4003,10 @@ packages:
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
undici@7.4.0:
resolution: {integrity: sha512-PUQM3/es3noM24oUn10u3kNNap0AbxESOmnssmW+dOi9yGwlUSi5nTNYl3bNbTkWOF8YZDkx2tCmj9OtQ3iGGw==}
engines: {node: '>=20.18.1'}
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
engines: {node: '>=4'}
@@ -5376,10 +5271,6 @@ snapshots:
inherits: 2.0.4
minimalistic-assert: 1.0.1
asn1@0.2.6:
dependencies:
safer-buffer: 2.1.2
assert-plus@1.0.0: {}
assert@1.5.1:
@@ -5404,8 +5295,6 @@ snapshots:
dependencies:
async-done: 1.3.2
asynckit@0.4.0: {}
atob@2.1.2: {}
automerge@1.0.1-preview.7:
@@ -5418,13 +5307,9 @@ snapshots:
dependencies:
possible-typed-array-names: 1.0.0
aws-sign2@0.7.0: {}
aws4@1.13.2: {}
axios@0.21.4:
dependencies:
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
transitivePeerDependencies:
- debug
@@ -5491,10 +5376,6 @@ snapshots:
mixin-deep: 1.3.2
pascalcase: 0.1.1
bcrypt-pbkdf@1.0.2:
dependencies:
tweetnacl: 0.14.5
binary-extensions@1.13.1: {}
binary-extensions@2.3.0: {}
@@ -5779,8 +5660,6 @@ snapshots:
caniuse-lite@1.0.30001692: {}
caseless@0.12.0: {}
chai@4.5.0:
dependencies:
assertion-error: 1.1.0
@@ -5908,10 +5787,6 @@ snapshots:
lodash.memoize: 3.0.4
source-map: 0.5.7
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
commander@2.20.3: {}
commander@6.2.1: {}
@@ -6050,10 +5925,6 @@ snapshots:
dash-ast@2.0.1: {}
dashdash@1.14.1:
dependencies:
assert-plus: 1.0.0
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.3
@@ -6144,8 +6015,6 @@ snapshots:
defined@1.0.1: {}
delayed-stream@1.0.0: {}
depd@2.0.0: {}
deps-sort@2.0.1:
@@ -6223,11 +6092,6 @@ snapshots:
is-plain-object: 2.0.4
object.defaults: 1.1.0
ecc-jsbn@0.1.2:
dependencies:
jsbn: 0.1.1
safer-buffer: 2.1.2
ee-first@1.1.1: {}
electron-to-chromium@1.5.80: {}
@@ -6767,7 +6631,9 @@ snapshots:
inherits: 2.0.4
readable-stream: 2.3.8
follow-redirects@1.15.9: {}
follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
debug: 4.4.0(supports-color@8.1.1)
for-each@0.3.3:
dependencies:
@@ -6779,16 +6645,8 @@ snapshots:
dependencies:
for-in: 1.0.2
forever-agent@0.6.1: {}
fork-stream@0.0.4: {}
form-data@2.3.3:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
forwarded@0.2.0: {}
fragment-cache@0.2.1:
@@ -6876,10 +6734,6 @@ snapshots:
get-value@2.0.6: {}
getpass@0.1.7:
dependencies:
assert-plus: 1.0.0
glob-parent@3.1.0:
dependencies:
is-glob: 3.1.0
@@ -7060,13 +6914,6 @@ snapshots:
dependencies:
glogg: 1.0.2
har-schema@2.0.0: {}
har-validator@5.1.5:
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
has-ansi@2.0.0:
dependencies:
ansi-regex: 2.1.1
@@ -7150,30 +6997,25 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
http-proxy-middleware@2.0.7:
http-proxy-middleware@3.0.3:
dependencies:
'@types/http-proxy': 1.17.15
http-proxy: 1.18.1
debug: 4.4.0(supports-color@8.1.1)
http-proxy: 1.18.1(patch_hash=8071c23044f455271f4d4074ae4c7b81beec17a03aefd158d5f4edd4ef751c11)(debug@4.4.0)
is-glob: 4.0.3
is-plain-obj: 3.0.0
is-plain-object: 5.0.0
micromatch: 4.0.8
transitivePeerDependencies:
- debug
- supports-color
http-proxy@1.18.1:
http-proxy@1.18.1(patch_hash=8071c23044f455271f4d4074ae4c7b81beec17a03aefd158d5f4edd4ef751c11)(debug@4.4.0):
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.15.9
follow-redirects: 1.15.9(debug@4.4.0)
requires-port: 1.0.0
transitivePeerDependencies:
- debug
http-signature@1.2.0:
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.2
sshpk: 1.18.0
https-browserify@1.0.0: {}
iconv-lite@0.4.24:
@@ -7382,8 +7224,6 @@ snapshots:
is-plain-obj@2.1.0: {}
is-plain-obj@3.0.0: {}
is-plain-object@2.0.4:
dependencies:
isobject: 3.0.1
@@ -7422,8 +7262,6 @@ snapshots:
dependencies:
which-typed-array: 1.1.18
is-typedarray@1.0.0: {}
is-unc-path@1.0.0:
dependencies:
unc-path-regex: 0.1.2
@@ -7461,8 +7299,6 @@ snapshots:
isobject@3.0.1: {}
isstream@0.1.2: {}
js-base64@3.7.7: {}
js-tokens@4.0.0: {}
@@ -7476,8 +7312,6 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbn@0.1.1: {}
jsbn@1.1.0: {}
jsesc@3.0.2: {}
@@ -7492,19 +7326,10 @@ snapshots:
json-stable-stringify-without-jsonify@1.0.1: {}
json-stringify-safe@5.0.1: {}
json5@2.2.3: {}
jsonparse@1.3.1: {}
jsprim@1.4.2:
dependencies:
assert-plus: 1.0.0
extsprintf: 1.3.0
json-schema: 0.4.0
verror: 1.10.0
jsprim@2.0.2:
dependencies:
assert-plus: 1.0.0
@@ -7889,8 +7714,6 @@ snapshots:
number-is-nan@1.0.1: {}
oauth-sign@0.9.0: {}
object-assign@4.1.1: {}
object-copy@0.1.0:
@@ -8104,8 +7927,6 @@ snapshots:
commander: 9.5.0
source-map-generator: 0.8.0
performance-now@2.1.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -8165,10 +7986,6 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
psl@1.15.0:
dependencies:
punycode: 2.3.1
pstree.remy@1.1.8: {}
public-encrypt@4.0.3:
@@ -8203,8 +8020,6 @@ snapshots:
dependencies:
side-channel: 1.1.0
qs@6.5.3: {}
querystring-es3@0.2.1: {}
queue-microtask@1.2.3: {}
@@ -8374,29 +8189,6 @@ snapshots:
is-absolute: 1.0.0
remove-trailing-separator: 1.1.0
request@2.88.2:
dependencies:
aws-sign2: 0.7.0
aws4: 1.13.2
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
form-data: 2.3.3
har-validator: 5.1.5
http-signature: 1.2.0
is-typedarray: 1.0.0
isstream: 0.1.2
json-stringify-safe: 5.0.1
mime-types: 2.1.35
oauth-sign: 0.9.0
performance-now: 2.1.0
qs: 6.5.3
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
require-directory@2.1.1: {}
require-main-filename@1.0.1: {}
@@ -8694,18 +8486,6 @@ snapshots:
sprintf-js@1.1.3: {}
sshpk@1.18.0:
dependencies:
asn1: 0.2.6
assert-plus: 1.0.0
bcrypt-pbkdf: 1.0.2
dashdash: 1.14.1
ecc-jsbn: 0.1.2
getpass: 0.1.7
jsbn: 0.1.1
safer-buffer: 2.1.2
tweetnacl: 0.14.5
stack-trace@0.0.10: {}
static-eval@0.2.4:
@@ -8993,11 +8773,6 @@ snapshots:
touch@3.1.1: {}
tough-cookie@2.5.0:
dependencies:
psl: 1.15.0
punycode: 2.3.1
transform-ast@2.4.4:
dependencies:
acorn-node: 1.8.2
@@ -9010,12 +8785,6 @@ snapshots:
tty-browserify@0.0.1: {}
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
tweetnacl@0.14.5: {}
type-check@0.3.2:
dependencies:
prelude-ls: 1.1.2
@@ -9137,6 +8906,8 @@ snapshots:
undici-types@6.20.0: {}
undici@7.4.0: {}
unicode-canonical-property-names-ecmascript@2.0.1: {}
unicode-match-property-ecmascript@2.0.0:

View File

@@ -23,6 +23,7 @@ import { produceArtifact } from '@/restful/sync';
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
import Gist from '@/utils/gist';
import { isPresent } from './producers/utils';
import { doh } from '@/utils/dns';
function preprocess(raw) {
for (const processor of PROXY_PREPROCESSORS) {
@@ -81,9 +82,9 @@ function parse(raw) {
if (['vless', 'vmess'].includes(proxy.type)) {
const isProxyUUIDValid = isValidUUID(proxy.uuid);
if (!isProxyUUIDValid) {
$.error(`UUID is invalid: ${proxy.name} ${proxy.uuid}`);
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
}
return isProxyUUIDValid;
// return isProxyUUIDValid;
}
return true;
});
@@ -184,6 +185,7 @@ async function processFn(
}
} else {
script = content;
$arguments = item.args.arguments || {};
}
}
@@ -235,8 +237,8 @@ function produce(proxies, targetPlatform, type, opts = {}) {
if (['vless', 'vmess'].includes(proxy.type)) {
const isProxyUUIDValid = isValidUUID(proxy.uuid);
if (!isProxyUUIDValid)
$.error(`UUID is invalid: ${proxy.name} ${proxy.uuid}`);
return isProxyUUIDValid;
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
// return isProxyUUIDValid;
}
return true;
@@ -326,6 +328,8 @@ export const ProxyUtils = {
MMDB,
Gist,
download,
isValidUUID,
doh,
};
function tryParse(parser, line) {
@@ -423,9 +427,14 @@ function lastParse(proxy) {
}
}
if (
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
proxy.type,
)
[
'trojan',
'tuic',
'hysteria',
'hysteria2',
'juicity',
'anytls',
].includes(proxy.type)
) {
proxy.tls = true;
}
@@ -509,6 +518,14 @@ function lastParse(proxy) {
proxy['obfs-password'] = proxy.obfs;
proxy.obfs = 'salamander';
}
if (
['hysteria2'].includes(proxy.type) &&
!proxy['obfs-password'] &&
proxy['obfs_password']
) {
proxy['obfs-password'] = proxy['obfs_password'];
delete proxy['obfs_password'];
}
if (['vless'].includes(proxy.type)) {
// 删除 reality-opts: {}
if (

View File

@@ -190,6 +190,8 @@ function URI_SS() {
// handle obfs
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
const shadowTlsMatch = content.match(/[?&]shadow-tls=([^&]+)/);
if (pluginMatch) {
const pluginInfo = (
'plugin=' + decodeURIComponent(pluginMatch[1])
@@ -233,6 +235,25 @@ function URI_SS() {
);
}
}
// Shadowrocket
if (shadowTlsMatch) {
const params = JSON.parse(Base64.decode(shadowTlsMatch[1]));
const version = getIfNotBlank(params['version']);
const address = getIfNotBlank(params['address']);
const port = getIfNotBlank(params['port']);
proxy.plugin = 'shadow-tls';
proxy['plugin-opts'] = {
host: getIfNotBlank(params['host']),
password: getIfNotBlank(params['password']),
version: version ? parseInt(version, 10) : undefined,
};
if (address) {
proxy.server = address;
}
if (port) {
proxy.port = parseInt(port, 10);
}
}
if (/(&|\?)uot=(1|true)/i.test(query)) {
proxy['udp-over-tcp'] = true;
}
@@ -321,7 +342,7 @@ function URI_VMess() {
};
const parse = (line) => {
line = line.split('vmess://')[1];
let content = Base64.decode(line);
let content = Base64.decode(line.replace(/\?.*?$/, ''));
if (/=\s*vmess/.test(content)) {
// Quantumult VMess URI format
const partitions = content.split(',').map((p) => p.trim());
@@ -678,6 +699,52 @@ function URI_VLESS() {
};
return { name, test, parse };
}
function URI_AnyTLS() {
const name = 'URI AnyTLS Parser';
const test = (line) => {
return /^anytls:\/\//.test(line);
};
const parse = (line) => {
line = line.split(/anytls:\/\//)[1];
// eslint-disable-next-line no-unused-vars
let [__, password, server, port, addons = '', name] =
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
password = decodeURIComponent(password);
port = parseInt(`${port}`, 10);
if (isNaN(port)) {
port = 443;
}
password = decodeURIComponent(password);
if (name != null) {
name = decodeURIComponent(name);
}
name = name ?? `AnyTLS ${server}:${port}`;
const proxy = {
type: 'anytls',
name,
server,
port,
password,
};
for (const addon of addons.split('&')) {
let [key, value] = addon.split('=');
key = key.replace(/_/g, '-');
value = decodeURIComponent(value);
if (['alpn'].includes(key)) {
proxy[key] = value ? value.split(',') : undefined;
} else if (['insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else {
proxy[key] = value;
}
}
return proxy;
};
return { name, test, parse };
}
function URI_Hysteria2() {
const name = 'URI Hysteria2 Parser';
const test = (line) => {
@@ -708,6 +775,7 @@ function URI_Hysteria2() {
] = /^(.*?)@(.*?)(:((\d+(-\d+)?)([,;]\d+(-\d+)?)*))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(
line,
);
/* eslint-enable no-unused-vars */
if (/^\d+$/.test(port)) {
port = parseInt(`${port}`, 10);
@@ -751,12 +819,23 @@ function URI_Hysteria2() {
if (params.obfs && params.obfs !== 'none') {
proxy.obfs = params.obfs;
}
proxy.ports = params.mport;
if (params.mport) {
proxy.ports = params.mport;
}
proxy['obfs-password'] = params['obfs-password'];
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
proxy['tls-fingerprint'] = params.pinSHA256;
let hop_interval = params['hop-interval'] || params['hop_interval'];
if (/^\d+$/.test(hop_interval)) {
proxy['hop-interval'] = parseInt(`${hop_interval}`, 10);
}
let keepalive = params['keepalive'];
if (/^\d+$/.test(keepalive)) {
proxy['keepalive'] = parseInt(`${keepalive}`, 10);
}
return proxy;
};
@@ -839,8 +918,11 @@ function URI_TUIC() {
const parse = (line) => {
line = line.split(/tuic:\/\//)[1];
// eslint-disable-next-line no-unused-vars
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
let [__, auth, server, port, addons = '', name] =
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
auth = decodeURIComponent(auth);
let [uuid, ...passwordParts] = auth.split(':');
let password = passwordParts.join(':');
port = parseInt(`${port}`, 10);
if (isNaN(port)) {
port = 443;
@@ -862,12 +944,14 @@ function URI_TUIC() {
for (const addon of addons.split('&')) {
let [key, value] = addon.split('=');
key = key.replace(/_/, '-');
key = key.replace(/_/g, '-');
value = decodeURIComponent(value);
if (['alpn'].includes(key)) {
proxy[key] = value ? value.split(',') : undefined;
} else if (['allow-insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else if (['fast-open'].includes(key)) {
proxy.tfo = true;
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value);
} else {
@@ -1005,6 +1089,7 @@ function Clash_All() {
const proxy = JSON.parse(line);
if (
![
'anytls',
'mieru',
'juicity',
'ss',
@@ -1517,6 +1602,7 @@ export default [
URI_Hysteria(),
URI_Hysteria2(),
URI_Trojan(),
URI_AnyTLS(),
Clash_All(),
Surge_Direct(),
Surge_SSH(),

View File

@@ -246,7 +246,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }

View File

@@ -243,7 +243,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }

View File

@@ -63,7 +63,7 @@ function Clash() {
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
// 匹配 short-id 冒号后面的值(包含空格和引号)
const afterReplace = raw.replace(
/short-id:([ ]*[^,\n}]*)/g,
/short-id:([ \t]*[^#\n,}]*)/g,
(matched, value) => {
const afterTrim = value.trim();
@@ -75,6 +75,8 @@ function Clash() {
// 是否被引号包裹
if (/^(['"]).*\1$/.test(afterTrim)) {
return `short-id: ${afterTrim}`;
} else if (['null'].includes(afterTrim)) {
return `short-id: ${afterTrim}`;
} else {
return `short-id: "${afterTrim}"`;
}

View File

@@ -20,6 +20,7 @@ import {
validCheck,
flowTransfer,
getRmainingDays,
normalizeFlowHeader,
} from '@/utils/flow';
function isObject(item) {
@@ -365,24 +366,35 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
if (output?.$file?.type === 'mihomoProfile') {
try {
let patch = YAML.safeLoad(script);
let config;
if (output?.$content) {
try {
config = YAML.safeLoad(output?.$content);
} catch (e) {
$.error(e.message ?? e);
}
}
// if (typeof patch !== 'object') patch = {};
if (typeof patch !== 'object')
throw new Error('patch is not an object');
output.$content = ProxyUtils.yaml.safeDump(
deepMerge(
{
proxies: await produceArtifact({
type:
output?.$file?.sourceType ||
'collection',
name: output?.$file?.sourceName,
platform: 'mihomo',
produceType: 'internal',
produceOpts: {
'delete-underscore-fields': true,
},
}),
},
config ||
(output?.$file?.sourceType === 'none'
? {}
: {
proxies: await produceArtifact({
type:
output?.$file?.sourceType ||
'collection',
name: output?.$file?.sourceName,
platform: 'mihomo',
produceType: 'internal',
produceOpts: {
'delete-underscore-fields': true,
},
}),
}),
patch,
),
);
@@ -413,7 +425,15 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
if($file.type === 'mihomoProfile') {
${script}
if(typeof main === 'function') {
const config = {
let config;
if ($content) {
try {
config = ProxyUtils.yaml.safeLoad($content);
} catch (e) {
console.log(e.message ?? e);
}
}
$content = ProxyUtils.yaml.safeDump(await main(config || ($file.sourceType === 'none' ? {} : {
proxies: await produceArtifact({
type: $file.sourceType || 'collection',
name: $file.sourceName,
@@ -423,8 +443,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
'delete-underscore-fields': true
}
}),
}
$content = ProxyUtils.yaml.safeDump(await main(config))
})))
}
} else {
${script}
@@ -830,7 +849,12 @@ function UselessFilter() {
}
// filter by regions
function RegionFilter(regions) {
function RegionFilter(input) {
let regions = input?.value || input;
if (!Array.isArray(regions)) {
regions = [];
}
const keep = input?.keep ?? true;
const REGION_MAP = {
HK: '🇭🇰',
TW: '🇹🇼',
@@ -847,7 +871,8 @@ function RegionFilter(regions) {
// this would be high memory usage
return proxies.map((proxy) => {
const flag = getFlag(proxy.name);
return regions.some((r) => REGION_MAP[r] === flag);
const selected = regions.some((r) => REGION_MAP[r] === flag);
return keep ? selected : !selected;
});
},
};
@@ -879,11 +904,19 @@ function buildRegex(str, ...options) {
}
// filter by proxy types
function TypeFilter(types) {
function TypeFilter(input) {
let types = input?.value || input;
if (!Array.isArray(types)) {
types = [];
}
const keep = input?.keep ?? true;
return {
name: 'Type Filter',
func: (proxies) => {
return proxies.map((proxy) => types.some((t) => proxy.type === t));
return proxies.map((proxy) => {
const selected = types.some((t) => proxy.type === t);
return keep ? selected : !selected;
});
},
};
}
@@ -1083,6 +1116,7 @@ function createDynamicFunction(name, script, $arguments, $options) {
flowTransfer,
validCheck,
getRmainingDays,
normalizeFlowHeader,
};
if ($.env.isLoon) {
return new Function(

View File

@@ -141,6 +141,7 @@ export default function Clash_Producer() {
'hysteria',
'hysteria2',
'juicity',
'anytls',
].includes(proxy.type)
) {
delete proxy.tls;

View File

@@ -10,6 +10,47 @@ export default function ClashMeta_Producer() {
return false;
} else if (['juicity'].includes(proxy.type)) {
return false;
} else if (
['ss'].includes(proxy.type) &&
![
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'aes-128-ccm',
'aes-192-ccm',
'aes-256-ccm',
'aes-128-gcm-siv',
'aes-256-gcm-siv',
'chacha20-ietf',
'chacha20',
'xchacha20',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'chacha8-ietf-poly1305',
'xchacha8-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305',
'lea-128-gcm',
'lea-192-gcm',
'lea-256-gcm',
'rabbit128-poly1305',
'aegis-128l',
'aegis-256',
'aez-384',
'deoxys-ii-256-128',
'rc4-md5',
'none',
].includes(proxy.cipher)
) {
// https://wiki.metacubex.one/config/proxies/ss/#cipher
return false;
}
return true;
})
@@ -105,6 +146,9 @@ export default function ClashMeta_Producer() {
password: proxy['shadow-tls-password'],
version: proxy['shadow-tls-version'],
};
delete proxy['shadow-tls-password'];
delete proxy['shadow-tls-sni'];
delete proxy['shadow-tls-version'];
}
}
@@ -160,6 +204,7 @@ export default function ClashMeta_Producer() {
'hysteria',
'hysteria2',
'juicity',
'anytls',
].includes(proxy.type)
) {
delete proxy.tls;

View File

@@ -1,6 +1,8 @@
import { isPresent } from './utils';
export default function Egern_Producer() {
const type = 'ALL';
const produce = (proxies, type, opts = {}) => {
const produce = (proxies, type) => {
// https://egernapp.com/zh-CN/docs/configuration/proxies
const list = proxies
.filter((proxy) => {
@@ -71,6 +73,7 @@ export default function Egern_Producer() {
return true;
})
.map((proxy) => {
const original = { ...proxy };
if (proxy.tls && !proxy.sni) {
proxy.sni = proxy.server;
}
@@ -120,10 +123,10 @@ export default function Egern_Producer() {
proxy.udp || proxy.udp_relay || proxy.udp_relay,
next_hop: proxy.next_hop,
};
if (proxy.plugin === 'obfs') {
proxy.obfs = proxy['plugin-opts'].mode;
proxy.obfs_host = proxy['plugin-opts'].host;
proxy.obfs_uri = proxy['plugin-opts'].path;
if (original.plugin === 'obfs') {
proxy.obfs = original['plugin-opts'].mode;
proxy.obfs_host = original['plugin-opts'].host;
proxy.obfs_uri = original['plugin-opts'].path;
}
} else if (proxy.type === 'hysteria2') {
proxy = {
@@ -141,9 +144,12 @@ export default function Egern_Producer() {
port_hopping: proxy.ports,
port_hopping_interval: proxy['hop-interval'],
};
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
if (
original['obfs-password'] &&
original.obfs == 'salamander'
) {
proxy.obfs = 'salamander';
proxy.obfs_password = proxy['obfs-password'];
proxy.obfs_password = original['obfs-password'];
}
} else if (proxy.type === 'tuic') {
proxy = {
@@ -215,7 +221,9 @@ export default function Egern_Producer() {
proxy.transport = {
http1: {
method: proxy['http-opts']?.method,
path: proxy['http-opts']?.path,
path: Array.isArray(proxy['http-opts']?.path)
? proxy['http-opts']?.path[0]
: proxy['http-opts']?.path,
headers: {
Host: Array.isArray(
proxy['http-opts']?.headers?.Host,
@@ -230,7 +238,9 @@ export default function Egern_Producer() {
proxy.transport = {
http2: {
method: proxy['h2-opts']?.method,
path: proxy['h2-opts']?.path,
path: Array.isArray(proxy['h2-opts']?.path)
? proxy['h2-opts']?.path[0]
: proxy['h2-opts']?.path,
headers: {
Host: Array.isArray(
proxy['h2-opts']?.headers?.Host,
@@ -288,7 +298,9 @@ export default function Egern_Producer() {
proxy.transport = {
http: {
method: proxy['http-opts']?.method,
path: proxy['http-opts']?.path,
path: Array.isArray(proxy['http-opts']?.path)
? proxy['http-opts']?.path[0]
: proxy['http-opts']?.path,
headers: {
Host: Array.isArray(
proxy['http-opts']?.headers?.Host,
@@ -326,6 +338,39 @@ export default function Egern_Producer() {
// skip_tls_verify: proxy['skip-cert-verify'],
};
}
if (
[
'http',
'socks5',
'ss',
'trojan',
'vless',
'vmess',
].includes(original.type)
) {
if (isPresent(original, 'shadow-tls-password')) {
if (original['shadow-tls-version'] != 3)
throw new Error(
`shadow-tls version ${original['shadow-tls-version']} is not supported`,
);
proxy.shadow_tls = {
password: original['shadow-tls-password'],
sni: original['shadow-tls-sni'],
};
} else if (
['shadow-tls'].includes(original.plugin) &&
original['plugin-opts']
) {
if (original['plugin-opts'].version != 3)
throw new Error(
`shadow-tls version ${original['plugin-opts'].version} is not supported`,
);
proxy.shadow_tls = {
password: original['plugin-opts'].password,
sni: original['plugin-opts'].host,
};
}
}
delete proxy.subName;
delete proxy.collectionName;

View File

@@ -341,10 +341,9 @@ function vmess(proxy) {
// udp
if (proxy.udp) {
result.append(`,udp=true`);
const ip_version =
ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
}
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString();
}
@@ -416,10 +415,9 @@ function vless(proxy) {
// udp
if (proxy.udp) {
result.append(`,udp=true`);
const ip_version =
ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
}
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
return result.toString();
}

View File

@@ -8,7 +8,7 @@ export default function Shadowrocket_Producer() {
if (opts['include-unsupported-proxy']) return true;
if (proxy.type === 'snell' && String(proxy.version) === '4') {
return false;
} else if (['mieru'].includes(proxy.type)) {
} else if (['mieru', 'anytls'].includes(proxy.type)) {
return false;
}
return true;
@@ -110,6 +110,21 @@ export default function Shadowrocket_Producer() {
proxy.servername = proxy.sni;
delete proxy.sni;
}
} else if (proxy.type === 'ss') {
if (
isPresent(proxy, 'shadow-tls-password') &&
!isPresent(proxy, 'plugin')
) {
proxy.plugin = 'shadow-tls';
proxy['plugin-opts'] = {
host: proxy['shadow-tls-sni'],
password: proxy['shadow-tls-password'],
version: proxy['shadow-tls-version'],
};
delete proxy['shadow-tls-password'];
delete proxy['shadow-tls-sni'];
delete proxy['shadow-tls-version'];
}
}
if (
@@ -163,6 +178,7 @@ export default function Shadowrocket_Producer() {
'hysteria',
'hysteria2',
'juicity',
'anytls',
].includes(proxy.type)
) {
delete proxy.tls;

View File

@@ -587,16 +587,14 @@ const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
};
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (includeUnsupportedProxy) {
if (proxy['hop-interval'])
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
? `${proxy['hop-interval']}s`
: proxy['hop-interval'];
if (proxy['ports'])
parsedProxy.server_ports = proxy['ports']
.split(/\s*,\s*/)
.map((p) => p.replace(/\s*-\s*/g, ':'));
}
if (proxy['hop-interval'])
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
? `${proxy['hop-interval']}s`
: proxy['hop-interval'];
if (proxy['ports'])
parsedProxy.server_ports = proxy['ports']
.split(/\s*,\s*/)
.map((p) => p.replace(/\s*-\s*/g, ':'));
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
@@ -641,6 +639,23 @@ const tuic5Parser = (proxy = {}) => {
smuxParser(proxy.smux, parsedProxy);
return parsedProxy;
};
const anytlsParser = (proxy = {}) => {
const parsedProxy = {
tag: proxy.name,
type: 'anytls',
server: proxy.server,
server_port: parseInt(`${proxy.port}`, 10),
password: proxy.password,
tls: { enabled: true, server_name: proxy.server, insecure: false },
};
if (/^\d+$/.test(proxy['idle-session-check-interval']))
parsedProxy.idle_session_check_interval = `${proxy['idle-session-check-interval']}s`;
if (/^\d+$/.test(proxy['idle-session-timeout']))
parsedProxy.idle_session_timeout = `${proxy['idle-session-timeout']}s`;
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
return parsedProxy;
};
const wireguardParser = (proxy = {}) => {
const local_address = ['ip', 'ipv6']
@@ -829,6 +844,9 @@ export default function singbox_Producer() {
case 'wireguard':
list.push(wireguardParser(proxy));
break;
case 'anytls':
list.push(anytlsParser(proxy));
break;
default:
throw new Error(
`Platform sing-box does not support proxy type: ${proxy.type}`,

View File

@@ -47,7 +47,11 @@ export default function Stash_Producer() {
: []),
].includes(proxy.cipher)) ||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
(proxy.type === 'vless' && proxy['reality-opts'])
(opts['include-unsupported-proxy']
? proxy.type === 'vless' &&
proxy['reality-opts'] &&
!['xtls-rprx-vision'].includes(proxy.flow)
: proxy.type === 'vless' && proxy['reality-opts'])
) {
return false;
}
@@ -247,6 +251,7 @@ export default function Stash_Producer() {
'hysteria',
'hysteria2',
'juicity',
'anytls',
].includes(proxy.type)
) {
delete proxy.tls;

View File

@@ -433,6 +433,9 @@ function ssh(proxy) {
return result.toString();
}
function http(proxy) {
if (proxy.headers && Object.keys(proxy.headers).length > 0) {
throw new Error(`headers is unsupported`);
}
const result = new Result(proxy);
const type = proxy.tls ? 'https' : 'http';
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);

View File

@@ -141,10 +141,19 @@ function mihomo(proxy, type, opts) {
dns: {
enable: true,
ipv6,
nameserver: [
'https://223.6.6.6/dns-query',
'https://120.53.53.53/dns-query',
],
'default-nameserver': opts?.defaultNameserver ||
proxy._defaultNameserver || [
'180.76.76.76',
'52.80.52.52',
'119.28.28.28',
'223.6.6.6',
],
nameserver: opts?.nameserver ||
proxy._nameserver || [
'https://doh.pub/dns-query',
'https://dns.alidns.com/dns-query',
'https://doh-pure.onedns.net/dns-query',
],
},
proxies: [
{

View File

@@ -23,7 +23,11 @@ export default function URI_Producer() {
) {
delete proxy.tls;
}
if (proxy.server && isIPv6(proxy.server)) {
if (
!['vmess'].includes(proxy.type) &&
proxy.server &&
isIPv6(proxy.server)
) {
proxy.server = `[${proxy.server}]`;
}
switch (proxy.type) {
@@ -387,6 +391,14 @@ export default function URI_Producer() {
break;
case 'hysteria2':
let hysteria2params = [];
if (proxy['hop-interval']) {
hysteria2params.push(
`hop-interval=${proxy['hop-interval']}`,
);
}
if (proxy['keepalive']) {
hysteria2params.push(`keepalive=${proxy['keepalive']}`);
}
if (proxy['skip-cert-verify']) {
hysteria2params.push(`insecure=1`);
}
@@ -493,6 +505,7 @@ export default function URI_Producer() {
'password',
'server',
'port',
'tls',
].includes(key)
) {
const i = key.replace(/-/, '_');
@@ -521,10 +534,13 @@ export default function URI_Producer() {
['disable-sni', 'reduce-rtt'].includes(key) &&
proxy[key]
) {
tuicParams.push(`${i}=1`);
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
} else if (proxy[key]) {
tuicParams.push(
`${i}=${encodeURIComponent(proxy[key])}`,
`${i.replace(
/-/g,
'_',
)}=${encodeURIComponent(proxy[key])}`,
);
}
}
@@ -539,6 +555,50 @@ export default function URI_Producer() {
)}`;
}
break;
case 'anytls':
let anytlsParams = [];
Object.keys(proxy).forEach((key) => {
if (
![
'name',
'type',
'password',
'server',
'port',
'tls',
].includes(key)
) {
const i = key.replace(/-/, '_');
if (['alpn'].includes(key)) {
if (proxy[key]) {
anytlsParams.push(
`${i}=${encodeURIComponent(
Array.isArray(proxy[key])
? proxy[key][0]
: proxy[key],
)}`,
);
}
} else if (['skip-cert-verify'].includes(key)) {
if (proxy[key]) {
anytlsParams.push(`insecure=1`);
}
} else if (proxy[key]) {
anytlsParams.push(
`${i.replace(/-/g, '_')}=${encodeURIComponent(
proxy[key],
)}`,
);
}
}
});
result = `anytls://${encodeURIComponent(proxy.password)}@${
proxy.server
}:${proxy.port}/?${anytlsParams.join('&')}#${encodeURIComponent(
proxy.name,
)}`;
break;
case 'wireguard':
let wireguardParams = [];

View File

@@ -11,6 +11,7 @@
* @documentation: https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46
*/
import { version } from '../package.json';
import $ from '@/core/app';
console.log(
`
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
@@ -18,7 +19,10 @@ console.log(
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
if ($.env.isNode) {
const dotenv = eval(`require("dotenv")`);
dotenv.config();
}
import migrate from '@/utils/migration';
import serve from '@/restful';

View File

@@ -89,8 +89,10 @@ async function doSync() {
const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY);
const subNames = [];
let enabledCount = 0;
allArtifacts.map((artifact) => {
if (artifact.sync && artifact.source) {
enabledCount++;
if (artifact.type === 'subscription') {
const subName = artifact.source;
const sub = findByName(allSubs, subName);
@@ -111,6 +113,13 @@ async function doSync() {
}
});
if (enabledCount === 0) {
$.info(
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
);
return;
}
if (subNames.length > 0) {
await Promise.all(
subNames.map(async (subName) => {

View File

@@ -3,6 +3,7 @@ import { COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
import { failed, success } from '@/restful/response';
import $ from '@/core/app';
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
import { formatDateTime } from '@/utils';
export default function register($app) {
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
@@ -59,7 +60,11 @@ function getCollection(req, res) {
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(name)}.json"`,
`attachment; filename="${encodeURIComponent(
`sub-store_collection_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(collection));
} else {

View File

@@ -5,7 +5,7 @@ import {
import { ProxyUtils } from '@/core/proxy-utils';
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
import { findByName } from '@/utils/database';
import { getFlowHeaders } from '@/utils/flow';
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
import $ from '@/core/app';
import { failed } from '@/restful/response';
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
@@ -259,7 +259,10 @@ async function downloadSubscription(req, res) {
$arguments.flowUrl,
);
if (flowInfo) {
res.set('subscription-userinfo', flowInfo);
res.set(
'subscription-userinfo',
normalizeFlowHeader(flowInfo),
);
}
}
} catch (err) {
@@ -293,10 +296,9 @@ async function downloadSubscription(req, res) {
}
res.set(
'subscription-userinfo',
[subUserInfo, flowInfo]
.filter((i) => i)
.join('; ')
.replace(/\s*;\s*;\s*/g, ';'),
normalizeFlowHeader(
[subUserInfo, flowInfo].filter((i) => i).join(';'),
),
);
}
@@ -556,7 +558,7 @@ async function downloadCollection(req, res) {
if (subUserInfo) {
res.set(
'subscription-userinfo',
subUserInfo.replace(/\s*;\s*;\s*/g, ';'),
normalizeFlowHeader(subUserInfo),
);
}
if (platform === 'JSON') {

View File

@@ -1,5 +1,5 @@
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { getFlowHeaders } from '@/utils/flow';
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
import { FILES_KEY } from '@/constants';
import { failed, success } from '@/restful/response';
import $ from '@/core/app';
@@ -9,6 +9,7 @@ import {
InternalServerError,
} from '@/restful/errors';
import { produceArtifact } from '@/restful/sync';
import { formatDateTime } from '@/utils';
export default function register($app) {
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
@@ -148,7 +149,7 @@ async function getFile(req, res) {
if (flowInfo) {
res.set(
'subscription-userinfo',
flowInfo.replace(/\s*;\s*;\s*/g, ';'),
normalizeFlowHeader(flowInfo),
);
}
}
@@ -209,7 +210,11 @@ function getWholeFile(req, res) {
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(name)}.json"`,
`attachment; filename="${encodeURIComponent(
`sub-store_file_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(file));
} else {

View File

@@ -2,7 +2,7 @@ import express from '@/vendor/express';
import $ from '@/core/app';
import migrate from '@/utils/migration';
import download from '@/utils/download';
import { syncArtifacts } from '@/restful/sync';
import { syncArtifacts, produceArtifact } from '@/restful/sync';
import { gistBackupAction } from '@/restful/miscs';
import { TOKENS_KEY } from '@/constants';
@@ -29,6 +29,29 @@ export default function serve() {
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
}
const $app = express({ substore: $, port, host });
if ($.env.isNode) {
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
if (be_prefix) {
if (!fe_be_path.startsWith('/')) {
throw new Error(
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
);
}
$.info(
`[BACKEND PREFIX] ${host}:${port}${fe_be_path} -> ${host}:${port}`,
);
$app.use((req, res, next) => {
if (req.path.startsWith(fe_be_path)) {
const newPath = req.url.replace(fe_be_path, '') || '/';
req.url = newPath;
next();
} else {
res.status(403).send();
}
});
}
}
// register routes
registerCollectionRoutes($app);
registerSubscriptionRoutes($app);
@@ -75,6 +98,39 @@ export default function serve() {
// 'Asia/Shanghai' // timeZone
);
}
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
const produce_cron = eval('process.env.SUB_STORE_PRODUCE_CRON');
if (produce_cron) {
$.info(`[PRODUCE CRON] ${produce_cron} enabled`);
const { CronJob } = eval(`require("cron")`);
produce_cron.split(/\s*;\s*/).map((item) => {
const [cron, type, name] = item.split(/\s*,\s*/);
new CronJob(
cron.trim(),
async function () {
try {
$.info(
`[PRODUCE CRON] ${type} ${name} ${cron} started`,
);
await produceArtifact({ type, name });
$.info(
`[PRODUCE CRON] ${type} ${name} ${cron} finished`,
);
} catch (e) {
$.error(
`[PRODUCE CRON] ${type} ${name} ${cron} error: ${
e.message ?? e
}`,
);
}
}, // onTick
null, // onComplete
true, // start
// 'Asia/Shanghai' // timeZone
);
});
}
const backend_download_cron = eval(
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
);
@@ -161,11 +217,15 @@ export default function serve() {
const staticFileMiddleware = express_.static(fe_path);
let be_share_rewrite = '/share/:type/:name';
let be_api_rewrite = '';
let be_download_rewrite = '';
let be_api = '/api/';
let be_download = '/download/';
let be_share = '/share/';
let be_download_rewrite = '';
let be_api_rewrite = '';
let be_share_rewrite = `${be_share}:type/:name`;
let prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX')
? fe_be_path
: '';
if (fe_be_path) {
if (!fe_be_path.startsWith('/')) {
throw new Error(
@@ -182,9 +242,9 @@ export default function serve() {
app.use(
be_share_rewrite,
createProxyMiddleware({
target: `http://127.0.0.1:${port}`,
target: `http://127.0.0.1:${port}${prefix}`,
changeOrigin: true,
pathRewrite: (path, req) => {
pathRewrite: async (path, req) => {
if (req.method.toLowerCase() !== 'get')
throw new Error('Method not allowed');
const tokens = $.read(TOKENS_KEY) || [];
@@ -196,34 +256,26 @@ export default function serve() {
(t.exp == null || t.exp > Date.now()),
);
if (!token) throw new Error('Forbbiden');
return path;
return req.originalUrl;
},
}),
);
app.use(
be_api_rewrite,
createProxyMiddleware({
target: `http://127.0.0.1:${port}`,
pathRewrite: (path) => {
const newPath = path.startsWith(be_api_rewrite)
? path.replace(be_api_rewrite, be_api)
: path;
return newPath.includes('?')
? `${newPath}&share=true`
: `${newPath}?share=true`;
target: `http://127.0.0.1:${port}${prefix}${be_api}`,
pathRewrite: async (path) => {
return path.includes('?')
? `${path}&share=true`
: `${path}?share=true`;
},
}),
);
app.use(
be_download_rewrite,
createProxyMiddleware({
target: `http://127.0.0.1:${port}`,
target: `http://127.0.0.1:${port}${prefix}${be_download}`,
changeOrigin: true,
pathRewrite: (path) => {
return path.startsWith(be_download_rewrite)
? path.replace(be_download_rewrite, be_download)
: path;
},
}),
);
}
@@ -243,10 +295,10 @@ export default function serve() {
$.info(`[FRONTEND] ${fe_address}:${fe_port}`);
if (fe_be_path) {
$.info(
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> http://127.0.0.1:${port}${be_api}`,
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_api_rewrite} -> ${host}:${port}${prefix}${be_api}`,
);
$.info(
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> http://127.0.0.1:${port}${be_download}`,
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> ${host}:${port}${prefix}${be_download}`,
);
$.info(
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,

View File

@@ -14,6 +14,7 @@ import { InternalServerError, RequestInvalidError } from '@/restful/errors';
import Gist from '@/utils/gist';
import migrate from '@/utils/migration';
import env from '@/utils/env';
import { formatDateTime } from '@/utils';
export default function register($app) {
// utils
@@ -27,7 +28,9 @@ export default function register($app) {
res.set('content-type', 'application/json')
.set(
'content-disposition',
'attachment; filename="sub-store.json"',
`attachment; filename="${encodeURIComponent(
`sub-store_data_${formatDateTime(new Date())}.json`,
)}"`,
)
.send(
$.env.isNode
@@ -98,6 +101,21 @@ async function gistBackupAction(action) {
const updated = settings.syncTime;
switch (action) {
case 'upload':
try {
content = $.read('#sub-store');
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
$.info(`下载备份, 与本地内容对比...`);
const onlineContent = await gist.download(
GIST_BACKUP_FILE_NAME,
);
if (onlineContent === content) {
$.info(`内容一致, 无需上传备份`);
return;
}
} catch (error) {
$.error(`${error.message ?? error}`);
}
// update syncTime
settings.syncTime = new Date().getTime();
$.write(settings, SETTINGS_KEY);

View File

@@ -15,46 +15,53 @@ export default function register($app) {
async function previewFile(req, res) {
try {
const file = req.body;
let content;
if (
file.source === 'local' &&
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
) {
content = file.content;
} else {
const errors = {};
content = await Promise.all(
file.url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(url, file.ua);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
let content = '';
if (file.type !== 'mihomoProfile') {
if (
!file.ignoreFailedRemoteFile &&
Object.keys(errors).length > 0
file.source === 'local' &&
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
content = file.content;
} else {
const errors = {};
content = await Promise.all(
file.url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(
url,
file.ua,
undefined,
file.proxy,
);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
}
if (file.mergeSources === 'localFirst') {
content.unshift(file.content);
} else if (file.mergeSources === 'remoteFirst') {
content.push(file.content);
if (
!file.ignoreFailedRemoteFile &&
Object.keys(errors).length > 0
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
}
if (file.mergeSources === 'localFirst') {
content.unshift(file.content);
} else if (file.mergeSources === 'remoteFirst') {
content.push(file.content);
}
}
}
// parse proxies

View File

@@ -134,11 +134,15 @@ export async function updateArtifactStore() {
settings.artifactStore = url;
settings.artifactStoreStatus = 'VALID';
} else {
$.error(`找不到 Sub-Store Gist`);
$.error(`找不到 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY})`);
settings.artifactStoreStatus = 'NOT FOUND';
}
} catch (err) {
$.error(`查找 Sub-Store Gist 时发生错误: ${err.message ?? err}`);
$.error(
`查找 Sub-Store Gist (${ARTIFACT_REPOSITORY_KEY}) 时发生错误: ${
err.message ?? err
}`,
);
settings.artifactStoreStatus = 'ERROR';
}
$.write(settings, SETTINGS_KEY);

View File

@@ -13,6 +13,7 @@ import {
} from '@/utils/flow';
import { success, failed } from './response';
import $ from '@/core/app';
import { formatDateTime } from '@/utils';
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
@@ -264,7 +265,11 @@ function getSubscription(req, res) {
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(name)}.json"`,
`attachment; filename="${encodeURIComponent(
`sub-store_subscription_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(sub));
} else {

View File

@@ -43,7 +43,7 @@ async function produceArtifact({
}) {
platform = platform || 'JSON';
if (type === 'subscription') {
if (['subscription', 'sub'].includes(type)) {
let sub;
if (name) {
const allSubs = $.read(SUBS_KEY);
@@ -190,7 +190,7 @@ async function produceArtifact({
}
// produce
return ProxyUtils.produce(proxies, platform, produceType, produceOpts);
} else if (type === 'collection') {
} else if (['collection', 'col'].includes(type)) {
const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name);
@@ -410,105 +410,116 @@ async function produceArtifact({
const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name);
if (!file) throw new Error(`找不到文件 ${name}`);
let raw;
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
raw = content;
} else if (url) {
const errors = {};
raw = await Promise.all(
url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
undefined,
undefined,
noCache,
);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
let raw = '';
if (file.type !== 'mihomoProfile') {
if (
ignoreFailedRemoteFile != null &&
ignoreFailedRemoteFile !== ''
content &&
!['localFirst', 'remoteFirst'].includes(mergeSources)
) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
}
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
raw = content;
} else if (url) {
const errors = {};
raw = await Promise.all(
url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
undefined,
undefined,
noCache,
);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
}
if (mergeSources === 'localFirst') {
raw.unshift(content);
} else if (mergeSources === 'remoteFirst') {
raw.push(content);
}
} else if (
file.source === 'local' &&
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
) {
raw = file.content;
} else {
const errors = {};
raw = await Promise.all(
file.url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
undefined,
undefined,
noCache,
);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
if (
ignoreFailedRemoteFile != null &&
ignoreFailedRemoteFile !== ''
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
if (
ignoreFailedRemoteFile != null &&
ignoreFailedRemoteFile !== ''
) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
}
if (
!fileIgnoreFailedRemoteFile &&
Object.keys(errors).length > 0
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
}
if (mergeSources === 'localFirst') {
raw.unshift(content);
} else if (mergeSources === 'remoteFirst') {
raw.push(content);
}
} else if (
file.source === 'local' &&
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
}
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
raw = file.content;
} else {
const errors = {};
raw = await Promise.all(
file.url
.split(/[\r\n]+/)
.map((i) => i.trim())
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(
url,
ua || file.ua,
undefined,
file.proxy || proxy,
undefined,
undefined,
noCache,
);
} catch (err) {
errors[url] = err;
$.error(
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
);
return '';
}
}),
);
}
if (file.mergeSources === 'localFirst') {
raw.unshift(file.content);
} else if (file.mergeSources === 'remoteFirst') {
raw.push(file.content);
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
if (
ignoreFailedRemoteFile != null &&
ignoreFailedRemoteFile !== ''
) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
}
if (
!fileIgnoreFailedRemoteFile &&
Object.keys(errors).length > 0
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
}
if (file.mergeSources === 'localFirst') {
raw.unshift(file.content);
} else if (file.mergeSources === 'remoteFirst') {
raw.push(file.content);
}
}
}
const files = (Array.isArray(raw) ? raw : [raw]).flat();
@@ -545,8 +556,10 @@ async function syncArtifacts() {
const allSubs = $.read(SUBS_KEY);
const allCols = $.read(COLLECTIONS_KEY);
const subNames = [];
let enabledCount = 0;
allArtifacts.map((artifact) => {
if (artifact.sync && artifact.source) {
enabledCount++;
if (artifact.type === 'subscription') {
const subName = artifact.source;
const sub = findByName(allSubs, subName);
@@ -567,6 +580,13 @@ async function syncArtifacts() {
}
});
if (enabledCount === 0) {
$.info(
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
);
return;
}
if (subNames.length > 0) {
await Promise.all(
subNames.map(async (subName) => {

View File

@@ -313,3 +313,41 @@ export function getRmainingDays(opt = {}) {
$.error(`getRmainingDays failed: ${e.message ?? e}`);
}
}
export function normalizeFlowHeader(flowHeaders) {
try {
// 使用 Map 保持顺序并处理重复键
const kvMap = new Map();
flowHeaders
.split(';')
.map((p) => p.trim())
.filter(Boolean)
.forEach((pair) => {
const eqIndex = pair.indexOf('=');
if (eqIndex === -1) return;
const key = pair.slice(0, eqIndex).trim();
const encodedValue = pair.slice(eqIndex + 1).trim();
// 只保留第一个出现的 key
if (!kvMap.has(key)) {
try {
// 解码 URI 组件并保留原始值作为 fallback
const decodedValue = decodeURIComponent(encodedValue);
kvMap.set(key, decodedValue);
} catch (e) {
kvMap.set(key, encodedValue);
}
}
});
// 拼接标准化字符串
return Array.from(kvMap.entries())
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`) // 重新编码保持兼容性
.join('; ');
} catch (e) {
$.error(`normalizeFlowHeader failed: ${e.message ?? e}`);
return flowHeaders;
}
}

View File

@@ -18,7 +18,9 @@ const ISOFlags = {
'🇧🇬': ['BG', 'BGR'],
'🇧🇭': ['BH', 'BHR'],
'🇧🇴': ['BO', 'BOL'],
'🇧🇳': ['BN', 'BRN'],
'🇧🇷': ['BR', 'BRA'],
'🇧🇹': ['BT', 'BTN'],
'🇧🇾': ['BY', 'BLR'],
'🇨🇦': ['CA', 'CAN'],
'🇨🇭': ['CH', 'CHE'],
@@ -40,6 +42,7 @@ const ISOFlags = {
'🇬🇪': ['GE', 'GEO'],
'🇬🇷': ['GR', 'GRC'],
'🇬🇹': ['GT', 'GTM'],
'🇬🇺': ['GU', 'GUM'],
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
'🇭🇷': ['HR', 'HRV'],
'🇭🇺': ['HU', 'HUN'],
@@ -59,12 +62,15 @@ const ISOFlags = {
'🇮🇷': ['IR', 'IRN'],
'🇮🇸': ['IS', 'ISL'],
'🇮🇹': ['IT', 'ITA'],
'🇱🇦': ['LA', 'LAO'],
'🇱🇰': ['LK', 'LKA'],
'🇱🇹': ['LT', 'LTU'],
'🇱🇺': ['LU', 'LUX'],
'🇱🇻': ['LV', 'LVA'],
'🇲🇦': ['MA', 'MAR'],
'🇲🇩': ['MD', 'MDA'],
'🇳🇬': ['NG', 'NGA'],
'🇲🇲': ['MM', 'MMR'],
'🇲🇰': ['MK', 'MKD'],
'🇲🇳': ['MN', 'MNG'],
'🇲🇴': ['MO', 'MAC', 'CTM'],
@@ -83,6 +89,7 @@ const ISOFlags = {
'🇵🇷': ['PR', 'PRI'],
'🇵🇹': ['PT', 'PRT'],
'🇵🇾': ['PY', 'PRY'],
'🇵🇬': ['PG', 'PNG'],
'🇷🇴': ['RO', 'ROU'],
'🇷🇸': ['RS', 'SRB'],
'🇷🇪': ['RE', 'REU'],
@@ -142,8 +149,10 @@ export function getFlag(name) {
'🇧🇬': ['Bulgaria', '保加利亚', '保加利亞'],
'🇧🇭': ['Bahrain', '巴林'],
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
'🇧🇳': ['Brunei', '文莱', '汶萊'],
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
'🇧🇴': ['Bolivia', '玻利维亚'],
'🇧🇹': ['Bhutan', '不丹', '不丹王国'],
'🇨🇦': [
'Canada',
'加拿大',
@@ -194,6 +203,7 @@ export function getFlag(name) {
],
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
'🇬🇷': ['Greece', '希腊', '希臘'],
'🇬🇺': ['Guam', '关岛', '關島'],
'🇬🇹': ['Guatemala', '危地马拉'],
'🇭🇰': [
'Hongkong',
@@ -254,11 +264,14 @@ export function getFlag(name) {
'🇮🇷': ['Iran', '伊朗'],
'🇮🇸': ['Iceland', '冰岛', '冰島'],
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
'🇱🇰': ['Sri Lanka', '斯里兰卡', '斯里蘭卡'],
'🇱🇦': ['Laos', '老挝', '老撾'],
'🇱🇹': ['Lithuania', '立陶宛'],
'🇱🇺': ['Luxembourg', '卢森堡'],
'🇱🇻': ['Latvia', '拉脱维亚', 'Latvija'],
'🇲🇦': ['Morocco', '摩洛哥'],
'🇲🇩': ['Moldova', '摩尔多瓦', '摩爾多瓦'],
'🇲🇲': ['Myanmar', '缅甸', '緬甸'],
'🇳🇬': ['Nigeria', '尼日利亚', '尼日利亞'],
'🇲🇰': ['Macedonia', '马其顿', '馬其頓'],
'🇲🇳': ['Mongolia', '蒙古'],
@@ -284,6 +297,7 @@ export function getFlag(name) {
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
'🇵🇷': ['Puerto Rico', '波多黎各'],
'🇵🇹': ['Portugal', '葡萄牙'],
'🇵🇬': ['Papua New Guinea', '巴布亚新几内亚'],
'🇵🇾': ['Paraguay', '巴拉圭'],
'🇷🇴': ['Romania', '罗马尼亚'],
'🇷🇸': ['Serbia', '塞尔维亚'],

View File

@@ -118,6 +118,7 @@ export default class Gist {
.get('/gists?per_page=100&page=1')
.then((response) => {
const gists = JSON.parse(response.body);
$.info(`获取到当前 GitHub 用户的 gist: ${gists.length}`);
for (let g of gists) {
if (g.description === this.key) {
return g;
@@ -279,7 +280,7 @@ export default class Gist {
return Promise.reject(err);
}
} else {
return Promise.reject('找不到 Sub-Store Gist');
return Promise.reject(`找不到 Sub-Store Gist (${this.key})`);
}
}
}

View File

@@ -126,7 +126,32 @@ function isValidUUID(uuid) {
);
}
function formatDateTime(date, format = 'YYYY-MM-DD_HH-mm-ss') {
const d = date instanceof Date ? date : new Date(date);
if (isNaN(d.getTime())) {
return '';
}
const pad = (num) => String(num).padStart(2, '0');
const replacements = {
YYYY: d.getFullYear(),
MM: pad(d.getMonth() + 1),
DD: pad(d.getDate()),
HH: pad(d.getHours()),
mm: pad(d.getMinutes()),
ss: pad(d.getSeconds()),
};
return format.replace(
/YYYY|MM|DD|HH|mm|ss/g,
(match) => replacements[match],
);
}
export {
formatDateTime,
isValidUUID,
ipAddress,
isIPv4,

View File

@@ -24,7 +24,7 @@ class ResourceCache {
this._cleanup();
}
_cleanup() {
_cleanup(prefix, expires) {
// clear obsolete cached resource
let clear = false;
Object.entries(this.resourceCache).forEach((entry) => {
@@ -35,7 +35,11 @@ class ResourceCache {
$.delete(`#${id}`);
clear = true;
}
if (new Date().getTime() - updated.time > this.expires) {
if (
new Date().getTime() - updated.time >
(expires ?? this.expires) ||
(prefix && id.startsWith(prefix))
) {
delete this.resourceCache[id];
clear = true;
}
@@ -52,10 +56,15 @@ class ResourceCache {
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
}
get(id) {
get(id, expires, remove) {
const updated = this.resourceCache[id] && this.resourceCache[id].time;
if (updated && new Date().getTime() - updated <= this.expires) {
return this.resourceCache[id].data;
if (updated) {
if (new Date().getTime() - updated <= (expires ?? this.expires))
return this.resourceCache[id].data;
if (remove) {
delete this.resourceCache[id];
this._persist();
}
}
return null;
}

View File

@@ -17,7 +17,12 @@ export default function express({ substore: $, port, host }) {
const express_ = eval(`require("express")`);
const bodyParser = eval(`require("body-parser")`);
const app = express_();
app.use(bodyParser.json({ verify: rawBodySaver, limit: '1mb' }));
app.use(
bodyParser.json({
verify: rawBodySaver,
limit: eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb',
}),
);
app.use(
bodyParser.urlencoded({ verify: rawBodySaver, extended: true }),
);

View File

@@ -337,45 +337,111 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
opts: options.opts,
});
} else if (isLoon || isSurge || isNode) {
worker = new Promise((resolve, reject) => {
const request = isNode
? eval("require('request')")
: $httpClient;
worker = new Promise(async (resolve, reject) => {
const body = options.body;
const opts = JSON.parse(JSON.stringify(options));
opts.body = body;
if (!isNode && opts.timeout) {
opts.timeout = opts.timeout || 8000;
if (opts.timeout) {
opts.timeout++;
let unit = 'ms';
// 这些客户端单位为 s
if (isSurge || isStash || isShadowRocket) {
opts.timeout = Math.ceil(opts.timeout / 1000);
unit = 's';
if (isNaN(opts.timeout)) {
opts.timeout = 8000;
}
if (!isNode) {
let unit = 'ms';
// 这些客户端单位为 s
if (isSurge || isStash || isShadowRocket) {
opts.timeout = Math.ceil(opts.timeout / 1000);
unit = 's';
}
// Loon 为 ms
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
}
// Loon 为 ms
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
}
request[method.toLowerCase()](opts, (err, response, body) => {
// if (err) {
// console.log(err);
// } else {
// console.log({
// statusCode:
// response.status || response.statusCode,
// headers: response.headers,
// body,
// });
// }
if (err) reject(err);
else
resolve({
statusCode: response.status || response.statusCode,
headers: response.headers,
body,
if (isNode) {
const undici = eval("require('undici')");
const {
ProxyAgent,
EnvHttpProxyAgent,
request,
interceptors,
} = undici;
const agentOpts = {
connect: {
rejectUnauthorized:
opts.strictSSL === false ||
opts.insecure === true
? false
: true,
},
bodyTimeout: opts.timeout,
headersTimeout: opts.timeout,
};
try {
const url = new URL(opts.url);
if (url.username || url.password) {
opts.headers = {
...(opts.headers || {}),
Authorization: `Basic ${Buffer.from(
`${url.username || ''}:${
url.password || ''
}`,
).toString('base64')}`,
};
}
const response = await request(opts.url, {
...opts,
method: method.toUpperCase(),
dispatcher: (opts.proxy
? new ProxyAgent({
...agentOpts,
uri: opts.proxy,
})
: new EnvHttpProxyAgent(agentOpts)
).compose(
interceptors.redirect({
maxRedirections: 3,
throwOnMaxRedirects: true,
}),
),
});
});
resolve({
statusCode: response.statusCode,
headers: response.headers,
body:
opts.encoding === null
? await response.body.arrayBuffer()
: await response.body.text(),
});
} catch (e) {
reject(e);
}
} else {
$httpClient[method.toLowerCase()](
opts,
(err, response, body) => {
// if (err) {
// console.log(err);
// } else {
// console.log({
// statusCode:
// response.status || response.statusCode,
// headers: response.headers,
// body,
// });
// }
if (err) reject(err);
else
resolve({
statusCode:
response.status || response.statusCode,
headers: response.headers,
body,
});
},
);
}
});
} else if (isGUIforCores) {
worker = new Promise(async (resolve, reject) => {

View File

@@ -24,6 +24,10 @@ function operator(proxies = [], targetPlatform, context) {
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
// const fs = eval(`require("fs")`)
// // const path = eval(`require("path")`)
// fs.writeFileSync('/tmp/1.txt', $content, "utf8");
// $arguments 为传入的脚本参数
@@ -48,8 +52,32 @@ function operator(proxies = [], targetPlatform, context) {
// scriptResourceCache 缓存
// 可参考 https://t.me/zhetengsha/1003
// const cache = scriptResourceCache
// cache.set(id, data)
// cache.get(id)
// 设置
// cache.set('a:1', 1)
// cache.set('a:2', 2)
// 获取
// cache.get('a:1')
// 支持第二个参数: 自定义过期时间
// 支持第三个参数: 是否删除过期项
// cache.get('a:2', 1000, true)
// 清理
// cache._cleanup()
// 支持第一个参数: 匹配前缀的项也一起删除
// 支持第二个参数: 自定义过期时间
// cache._cleanup('a:', 1000)
// 关于缓存时长
// 拉取 Sub-Store 订阅时, 会自动拉取远程订阅
// 远程订阅缓存是 1 小时, 缓存的唯一 key 为 url+ user agent. 可通过前端的刷新按钮刷新缓存. 或使用参数 noCache 来禁用缓存. 例: 内部配置订阅链接时使用 http://a.com#noCache, 外部使用 sub-store 链接时使用 https://sub.store/download/1?noCache=true
// 当使用相关脚本时, 若在对应的脚本中使用参数开启缓存, 可设置持久化缓存 sub-store-csr-expiration-time 的值来自定义默认缓存时长, 默认为 172800000 (48 * 3600 * 1000, 即 48 小时)
// 🎈Loon 可在插件中设置
// 其他平台同理, 持久化缓存数据在 JSON 里
// ProxyUtils 为节点处理工具
// 可参考 https://t.me/zhetengsha/1066
@@ -69,6 +97,7 @@ function operator(proxies = [], targetPlatform, context) {
// Gist, // Gist 类
// download, // 内部的下载方法, 见 backend/src/utils/download.js
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
// isValidUUID, // 辅助判断是否为有效的 UUID
// }
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009