Compare commits

..

1202 Commits

Author SHA1 Message Date
xream
fe804fcf2c release: bump version 2025-08-08 12:43:53 +08:00
xream
c7c9b21f79 feat: 本地订阅支持混写单行 YAML
Some checks failed
build / build (push) Has been cancelled
2025-08-04 10:48:52 +08:00
xream
d84f761b5d feat: Loon tls-name 和 sni 若同时存在, 以后面的为准 2025-08-03 15:36:09 +08:00
xream
b7d7346ef7 feat: geo 增加多哥和伊拉克 2025-08-03 10:27:46 +08:00
xream
cbfe528c5e feat: 订阅流量信息中的 expire <=0 时, 视为空 2025-08-01 13:09:11 +08:00
xream
59ea2bd174 feat: 获取订阅时, 总是检查是否包含有效节点 2025-07-29 11:59:55 +08:00
xream
1c9ae2a079 feat: 支持 GitHub 加速代理(前端需 >= 2.15.58) 2025-07-28 14:31:39 +08:00
xream
22e39dc18f feat: 单条订阅确保没有 subscriptions 字段 2025-07-28 11:26:18 +08:00
xream
4276869033 refactor: 非 Node 环境的 req query params decode 2025-07-27 22:47:43 +08:00
xream
c902ad8c87 fix: 修复 sing-box vless 传输层
Some checks failed
build / build (push) Has been cancelled
2025-07-27 00:36:37 +08:00
xream
10fe493162 feat: Egern shadowsocks+shadow-tls 支持 udp port
Some checks failed
build / build (push) Has been cancelled
2025-07-26 15:57:47 +08:00
xream
3f9867512b feat: 流量四舍五入优化
Some checks failed
build / build (push) Has been cancelled
2025-07-22 17:40:50 +08:00
xream
8ab694efcc doc: README 2025-07-21 09:33:31 +08:00
xream
4977c4ac43 feat: 统一响应; 分享报错隐藏详情防止原始信息泄露
Some checks failed
build / build (push) Has been cancelled
2025-07-20 13:07:47 +08:00
xream
84144ad057 feat: sub-store.json 初始化时, 支持读取 Base64 内容
Some checks failed
build / build (push) Has been cancelled
2025-07-19 13:53:10 +08:00
xream
4c871964b0 feat: 手动还原支持 Base64 文本文件 2025-07-19 12:55:32 +08:00
xream
b3d66d42ff feat: 处理 clash 系和 sing-box 的 Early Data 2025-07-19 06:47:24 +08:00
xream
65cdaa0946 feat: Gist 备份默认为 Base64 编码方式 2025-07-18 17:21:11 +08:00
xream
a8135c7d6c feat: Gist 备份恢复增加 Base64 编码方式 2025-07-18 17:12:51 +08:00
xream
d752690129 feat: Node 环境使用 SUB_STORE_DATA_URL 恢复备份后, 支持 SUB_STORE_DATA_URL_POST 执行自定义命令 2025-07-18 16:10:26 +08:00
xream
43f7ae4a9a feat: 支持从 Gist 恢复时保留当前 GitHub Token 2025-07-18 14:54:13 +08:00
xream
9b3f76be19 fix: 尝试修复上传 Gist 2025-07-18 13:37:35 +08:00
xream
7310d9bd66 feat: Node 运行环境支持 SOCKS5 代理 2025-07-18 13:13:43 +08:00
xream
d1551c5644 feat: 备份数据到 Gist 时, 清除 GitHub Token. 恢复后请重新设置 GitHub Token 2025-07-18 10:14:18 +08:00
xream
785a715ce2 feat: 正式弃用 SUB_STORE_BACKEND_CRON 和 SUB_STORE_CRON, 请使用 SUB_STORE_BACKEND_SYNC_CRON 2025-07-17 18:49:34 +08:00
xream
86b828fd40 feat: 优化 Surge ws 传输层引号解析 2025-07-12 21:06:02 +08:00
xream
2a8db4a21c feat: 订阅流量信息中的数值取整以兼容部分客户端解析; 不包含有效节点的订阅不写入乐观缓存 2025-07-12 11:11:40 +08:00
xream
d0da52fff9 doc: demo.js 2025-07-12 09:26:04 +08:00
xream
146c2d966f feat: Surge username password 逻辑优化 2025-07-11 10:36:43 +08:00
xream
8059a23cf9 doc: demo.js 2025-07-10 12:37:19 +08:00
xream
437c95925e fix: 修复 sing-box 和 mihomo 的 ip-version 2025-07-10 12:32:11 +08:00
xream
6bbb3a1ccf fix: 修复 sing-box shadowsocks+shadow-tls 2025-07-10 08:00:39 +08:00
xream
dfaa02aa1a feat: 节点本地内容支持 JSON5 2025-07-10 01:37:16 +08:00
xream
5b554b27e4 feat: 放宽 sing-box VLESS flow 逻辑 2025-07-10 01:28:15 +08:00
xream
1d1ac03e44 fix: 修复 Egern transport 兼容性 2025-07-08 21:59:16 +08:00
xream
9ce1ebb816 doc: demo.js 2025-07-08 09:43:31 +08:00
xream
73fd36ceca fix: 修复 snell 版本过滤范围 2025-07-01 20:56:55 +08:00
xream
4e1b776785 fix: 修复阿里 httpdns edns 2025-06-25 19:36:08 +08:00
xream
7e6476aecd release: bump version 2025-06-23 21:37:50 +08:00
xream
e043a37b59 Merge pull request #460 from Ayideyia/master
适配下游客户端API
2025-06-23 21:37:24 +08:00
啊伊的伊阿
8b605e10a0 适配下游客户端API 2025-06-23 21:33:49 +08:00
xream
1945455ba6 feat: anytls 支持 min-idle-session 2025-06-19 10:38:20 +08:00
xream
427faed8d8 doc: README 2025-06-14 12:02:32 +08:00
xream
843cff42c4 feat: token 唯一性检测增加 type 和 name 2025-06-09 18:42:35 +08:00
xream
80f44884fb feat: SOCKS5 解析去除密码首尾双引号 2025-06-09 12:37:32 +08:00
xream
6cfcd1c6e2 feat: ProxyUtils 新增 JSON5 2025-06-05 11:01:41 +08:00
xream
956fff1b98 doc: README 2025-06-03 16:42:01 +08:00
xream
bd2b3f3fdf feat: 订阅和文件的远程链接支持使用换行混写三种格式 1. 完整远程链接 2. 类似 /api/file/name 的内部文件调用路径 3. 本地文件的绝对路径 2025-06-03 00:10:45 +08:00
xream
2093baaedb fix: 脚本链接为路径时带参解析 2025-06-02 23:17:47 +08:00
xream
1cd740aca0 feat: sing-box VLESS packet_encoding 2025-06-02 20:39:15 +08:00
xream
0f1b61b5a7 feat: 支持本地文件 2025-06-01 11:54:32 +08:00
xream
88283211cb feat: 若设置 $options._res.headers, 拉取文件时将设置自定义响应头 2025-05-28 13:46:57 +08:00
xream
1874c20c6d Stash 正式版支持 VLESS REALITY(xtls-rprx-vision) 2025-05-27 19:46:31 +08:00
xream
4558ee8c91 feat: VMess 支持 kcp/quic(正确处理 type, host, path, fp, alpn, tls等参数) 2025-05-27 03:01:28 +08:00
xream
cd2fd624db feat: Shadowrocket 支持 anytls 2025-05-26 17:24:39 +08:00
xream
0c17e14d9f feat: 为 env 响应增加如何使用前端搭配后端的引导说明 2025-05-25 00:58:06 +08:00
xream
f0c0361174 feat: Node.js 环境中 JSON 数据文件校验失败后会备份原文件, 创建新文件 2025-05-24 18:40:30 +08:00
xream
fe8e64dfbf fix: 修复 QX VLESS TLS 2025-05-23 22:36:08 +08:00
xream
f962b41ccb feat: 单条订阅和文件支持链接参数 produceType raw, 此时返回原始数据的数组 2025-05-22 16:09:35 +08:00
xream
75e4225d9c feat: 预处理支持 Base64 兜底 2025-05-22 15:17:38 +08:00
xream
0b22492fbc fix: 修复 SOCKS5 URI 2025-05-21 01:39:30 +08:00
xream
7d35911f7f feat: 日志增加 body JSON limit 2025-05-20 21:16:30 +08:00
xream
960706b1cc feat: Shadowrocket 支持前置代理. 补充 demo.js 说明 2025-05-18 17:21:54 +08:00
xream
ebb5ef68cb feat: Node.js 环境下 API / 路由不自动跳转到 sub-store.vercel.app 2025-05-17 22:49:12 +08:00
xream
7f67f57031 feat: 单条订阅和文件支持链接参数 produceType raw, 此时返回原始数据的数组 2025-05-17 20:22:24 +08:00
xream
9dabe7826a fix: 修复 Surge/Loon VMess aead 2025-05-12 12:26:14 +08:00
xream
3daa85683b fix: 修复 QX VMess aead 2025-05-12 11:06:13 +08:00
xream
1adb27bbcf doc: demo.js 2025-05-12 01:42:42 +08:00
xream
93bbb3b345 feat: 兼容 xishang0128/sparkle 的 JavaScript 覆写; ProxyUtils 新增 Buffer, Base64 2025-05-11 16:21:28 +08:00
xream
0f10978b65 feat: QX 正式支持 SS2022 2025-05-07 02:59:19 +08:00
xream
7029802b88 feat: 兼容非标 Shadowsocks URI 输入 2025-04-28 13:55:15 +08:00
xream
ecdbd01bc6 feat: sing-box Hysteria up/down 跟文档不一致, 但是懒得全转, 只处理最常见的 Mbps 2025-04-28 10:23:30 +08:00
xream
17a50cf0ec feat: 兼容 Shadowrocket 非标 VMess URI 输入中的 peer(sni) 2025-04-27 09:45:14 +08:00
xream
b70c689b61 feat: 输出到 Clash/Stash/Shadowrocket 时, 会过滤掉配置了前置代理的节点, 并提示使用对应的功能 2025-04-26 13:46:58 +08:00
xream
dde77b5333 feat: Stash 输出中过滤掉有前置代理的节点, 并在日志中提示 2025-04-22 09:42:32 +08:00
xream
6285475873 feat: 忽略失败的远程选择支持开启通知(前端 >= 2.15.17) 2025-04-21 19:28:58 +08:00
xream
f80a34d830 feat: 简单实现了 SUB_STORE_MMDB_CRON 定时更新 MMDB. ASN: SUB_STORE_MMDB_ASN_PATH, SUB_STORE_MMDB_ASN_URL. COUNTRY: SUB_STORE_MMDB_COUNTRY_PATH, SUB_STORE_MMDB_COUNTRY_URL; 脚本中新增 ProxyUtils.downloadFile 方便下载二进制文件. 2025-04-21 18:25:00 +08:00
xream
5473a9b627 fix: SS 解析增加默认节点名 2025-04-20 20:10:08 +08:00
xream
4a19d5a0df fix: 修复 URI 输出 2025-04-19 16:44:40 +08:00
xream
8a4f510ca5 feat: sing-box 输出支持 brutal 2025-04-18 22:49:19 +08:00
xream
32d968bb8e fix: 修复 URI 输出 2025-04-18 15:04:06 +08:00
xream
edb1b9f6dc feat: AnyTLS URI 支持 UDP 参数 2025-04-18 12:24:31 +08:00
xream
fa813a0040 feat: Loon 输入输出正式支持 VLESS XTLS/REALITY, VMess REALITY 2025-04-17 09:57:56 +08:00
xream
db2be960ac fix: 修复 Loon block-quic 参数 2025-04-16 07:28:28 +08:00
xream
e2ed426d96 feat: 支持 QX udp-over-tcp=true/sp.v1/sp.v2 2025-04-14 15:39:13 +08:00
xream
6331cdd8cc feat: 支持 QX udp-over-tcp=true/sp.v1; mihomo UDP over TCP 的协议版本默认 1, sing-box 默认为 2 2025-04-14 15:28:35 +08:00
xream
36fc22d9da fix: 修复 TUIC congestion-controller 2025-04-13 03:28:11 +08:00
xream
a9386242cd doc: demo.js 2025-04-11 22:49:12 +08:00
xream
8a128ee31b feat: 支持 Loon block-quic 参数 2025-04-11 22:44:12 +08:00
xream
c7f9326ef9 doc: demo.js 2025-04-11 15:21:41 +08:00
xream
082b0a56ad feat: 拉取文件时 日志输出 User-Agent; 脚本上下文参数 $options 中新增 _req 字段, 包含请求信息 2025-04-08 12:48:38 +08:00
xream
e4e0c7e69c feat: Shadowrocket VMess ws 传输层增加默认 path 2025-04-03 22:33:31 +08:00
xream
06acaa905a feat: Stash 正式版支持 SS2022, 测试版(>=3.1.0) 支持 VLESS REALITY(xtls-rprx-vision) 2025-04-03 15:47:35 +08:00
xream
bd4ab1440a docs(README): 增加赞助商信息 2025-04-03 15:23:15 +08:00
xream
b4e24a9bc7 feat: Loon 输入输出支持 VLESS XTLS/REALITY, VMess REALITY. 需 includeUnsupportedProxy 或 build >= 842 自动开启) 2025-04-01 18:22:28 +08:00
xream
7c68c272ee feat: QX 输入值支持 = 2025-03-29 19:52:40 +08:00
xream
a080d94f8d feat: QX 使用 includeUnsupportedProxy 参数开启 Shadowsocks 2022 2025-03-29 14:17:59 +08:00
xream
dd03f0a25f feat: 正则排序支持顺序/倒序/原顺序(前端 > 2.15.10) 2025-03-28 12:46:51 +08:00
xream
b677c01c3f doc: 标记 Clash Deprecated 2025-03-27 19:53:46 +08:00
xream
96e6f63c88 feat: 脚本链接叠加参数调整 2025-03-27 12:52:19 +08:00
xream
2c479aceef feat: Loon 解析器支持参数 resourceUrlOnly 仅使用远程资源, 忽略 Loon 自身解析数据 2025-03-26 00:26:31 +08:00
xream
6d488007f2 feat: 优化日志, Loon 解析器自动读取 build 2025-03-25 23:58:28 +08:00
xream
27fa8aca15 feat: VLESS 兼容 Shadowrocket 传输层 none 2025-03-25 23:35:23 +08:00
xream
e87eaddb60 feat: Loon 输入输出支持 VLESS REALITY(flow 为 xtls-rprx-vision). 需 includeUnsupportedProxy 或 build >= 838 自动开启) 2025-03-25 22:22:29 +08:00
xream
2805a0b477 feat: 调整 User-Agent 判断 2025-03-25 17:49:47 +08:00
xream
d4378025f9 fix: 修复 ss shadow-tls 2025-03-23 14:32:24 +08:00
xream
81a0cfdb4f feat: 更新文件时, 更新同步配置; 更新单条订阅/组合订阅时, 更新 mihomo 覆写 2025-03-21 00:36:42 +08:00
xream
32cdddd934 fix: 修复重置后端数据后无默认字段的问题 2025-03-20 22:03:06 +08:00
Aritro37
883e091930 fix: 修复聚合模式下,名称带有中文或特殊符号的分享token判断异常的问题 2025-03-20 21:57:42 +08:00
xream
9e1803e795 release: bump version 2025-03-19 16:11:49 +08:00
Aritro37
a75a845c24 perf: 增加 MERGE 模式下的信息输出 2025-03-19 16:08:48 +08:00
Aritro37
ef7bfb11f7 fix: 修复 SUB_STORE_FRONTEND_PATH 使用绝对目录时前端资源 Content-Type 响应错误的问题 2025-03-19 15:52:37 +08:00
Aritro37
63d5b3e6f7 feat: 引入SUB_STORE_BACKEND_MERGE 变量实现前后端端口合并及安全增强
1. 新增SUB_STORE_BACKEND_MERGE配置变量,支持功能整合模式:
   - 当设置SUB_STORE_BACKEND_MERGE为非空任意值时,后端支持同时处理API和前端资源请求
   - 新增配置示例:
     #合并前后端端口
     SUB_STORE_BACKEND_MERGE=true
     #设置接口安全地址
     SUB_STORE_FRONTEND_BACKEND_PATH=/safe-api
     #设置前端文件的路径
     SUB_STORE_FRONTEND_PATH=./dist
     #后端监听的端口
     SUB_STORE_BACKEND_API_PORT=3000
     #后端监听的HOST
     SUB_STORE_BACKEND_API_HOST="127.0.0.1"

2. 合并后支持前端在子路由界面刷新:
   - 原前端在subs、files、sync等页面刷新时会出现404问题,合并后修复了该问题
2025-03-19 15:26:17 +08:00
Aritro37
5c98e6ac9f fix: 修复使用 .env 时 /api/utils/env 接口中的 env 字段为空的问题 2025-03-19 15:07:01 +08:00
xream
e320c258fa chore: 日志 2025-03-18 13:34:50 +08:00
Aritro37
753523cdfe Perf: 提前加载 .env;后端复用前端 Path 2025-03-17 22:01:38 +08:00
xream
94c313e9b9 Merge pull request #430 from Aritro37/master
feat: 支持通过.env配置环境变量,后端支持设置前置路由
2025-03-17 17:07:37 +08:00
Aritro37
28b13dd7ac feat: 支持通过.env配置环境变量,后端支持设置前置路由 2025-03-17 17:04:24 +08:00
xream
d7930bfc6b feat: 增加更多的同步配置日志 2025-03-16 15:48:47 +08:00
xream
b95eb39694 feat: undici 配置重定向 2025-03-15 22:50:47 +08:00
xream
5470bceee1 fix: 处理 YAML short-idnull 的情况 2025-03-15 16:29:19 +08:00
xream
5c761a4137 feat: 增强 VMess URI 解析兼容性; 修改导出文件名格式 2025-03-13 20:19:45 +08:00
xream
1fe54a699a fix: 修复 VMess URI IPv6 格式 2025-03-13 19:26:53 +08:00
xream
78037af70c feat: url 支持 credentials; 修改导出文件名格式 2025-03-13 17:42:48 +08:00
xream
54c9e66bab feat: patch http-proxy; 使用 undici 替代 request 2025-03-13 13:02:19 +08:00
xream
e7bd21cd34 fix: 修复文件预览未使用代理策略的问题 2025-03-12 19:44:54 +08:00
xream
422e161c5b feat: 升级 http-proxy-middleware 2025-03-12 15:30:14 +08:00
xream
2f7f9a93e2 feat: mihomo hysteria2 兼容 obfs_password 字段 2025-03-10 23:06:33 +08:00
xream
bd0a2b3eeb feat: Hysteria2 URI 输入输出支持 hop-interval 和 keepalive 参数, 为保证兼容性, 输出时多端口暂时保持使用 mport 参数 2025-03-10 19:52:15 +08:00
xream
515ba0183d feat: sing-box hop_interval 和 server_ports 不需要 includeUnsupportedProxy 2025-03-10 19:36:41 +08:00
xream
2828122098 feat: Stash 使用 includeUnsupportedProxy 参数开启 XTLS-uTLS-Vision-REALITY(版本>=2.8.0 时自动开启) 2025-03-07 14:09:56 +08:00
xream
3ab0bfdca5 feat: Node.js 环境支持自定义 JSON Body limit, 例: SUB_STORE_BODY_JSON_LIMIT=10mb 2025-03-07 13:59:22 +08:00
xream
177ec57a81 feat: 验证 mihomo ss cipher 2025-03-05 15:25:16 +08:00
xream
32407e6071 fix: 修复 Surge shadow-tls-password 引号解析 2025-03-04 22:36:47 +08:00
xream
fb4b606fb3 feat: SurgeMac mihomo 配置中支持自定义 DNS 2025-03-04 20:11:22 +08:00
xream
b27d348d94 feat: 支持 AnyTLS URI 2025-03-03 20:52:31 +08:00
xream
49cd9762db fix: 修复 Egern 输出 2025-03-03 10:42:44 +08:00
xream
109752b5c3 feat(wip): 本地脚本支持传入参数 2025-03-02 19:02:15 +08:00
xream
0092efd95c 区域过滤和协议过滤支持保留模式和过滤模式(后端需 >= 2.17.0, 前端需 >= 2.15.0) 2025-03-02 11:06:33 +08:00
xream
a53748ebda feat: Surge 输出会判断 HTTP 是否 headers 字段 2025-03-01 21:43:14 +08:00
xream
592dc36f81 feat: 文件类型为 mihomo 配置时, 来源可以为无 2025-03-01 08:45:17 +08:00
xream
cd51eaae0a feat: 扩展 scriptResourceCache 缓存, 详见 demo.js 2025-02-28 15:54:04 +08:00
xream
bdca115229 feat: 支持使用环境变量 SUB_STORE_PRODUCE_CRON 在后台定时处理订阅, 格式为 0 */2 * * *,sub,a;0 */3 * * *,col,b 2025-02-28 14:07:35 +08:00
xream
ab632087aa fix: mihomo 覆写可以多次使用 2025-02-27 23:37:39 +08:00
xream
a84c2cc6cd feat: 规范化 subscription-userinfo 2025-02-27 23:23:32 +08:00
xream
d28c8eb9c1 feat: geo 更新 2025-02-27 17:27:15 +08:00
xream
4cc562f48c feat: 内容无变化时 不进行上传; 增加 gist 数量日志 2025-02-26 18:50:11 +08:00
xream
a36de02496 feat: sing-box 支持 anytls 2025-02-23 09:48:09 +08:00
xream
47f09cb6b8 fix: 修复 short-id 正则 2025-02-22 14:25:06 +08:00
xream
01ff77fa99 feat: 支持 Shadowrocket Shadowsocks 输入中的 Shadow TLS 参数 2025-02-21 01:44:34 +08:00
xream
24c448b8ac fix: 修复 Shadowrocket 输出的 Shadow TLS 2025-02-21 00:50:38 +08:00
xream
e56bc1eed4 fix: uuid 只辅助判断, 不直接过滤 2025-02-20 22:52:35 +08:00
xream
48550cb71c fix: 修复 Egern http 传输层 2025-02-20 22:24:39 +08:00
xream
3d835c63d6 fix: 修复 Loon ip-mode 2025-02-19 17:15:31 +08:00
xream
daaa4a70d4 feat: 支持 anytls 协议 2025-02-19 17:01:40 +08:00
xream
6c2e5fe9ca feat: Egern 支持 Shadow TLS 2025-02-18 15:07:24 +08:00
xream
879b728600 fix: UUID 2025-02-16 05:05:33 +08:00
xream
01c183c41a fix: 修复 TUIC URI 2025-02-15 20:47:34 +08:00
xream
4234dc8b20 fix: 文件类型为 mihomo 配置时, 不应处理本地或远程内容字段 2025-02-15 20:32:29 +08:00
xream
31053ca0d1 feat: 仅匹配 UUIDv4 2025-02-15 19:58:34 +08:00
xream
7233c7816f feat: 兼容更多 TUIC URI 字段 2025-02-14 23:27:01 +08:00
xream
248fe8cdcc feat: 下载文件名增加前后缀 2025-02-14 15:39:13 +08:00
xream
27ceb9f8ca feat: 兼容 v2rayN 非标 TUIC URI 2025-02-13 20:26:59 +08:00
xream
7ec1a19ff8 doc: README 2025-02-12 22:39:18 +08:00
xream
2b4cefcc12 fix: 修复 Shadowsocks URI 解析 2025-02-12 19:21:24 +08:00
xream
f92c4799d1 feat: 支持 v2ray SOCKS URI 的输入和输出 2025-02-12 03:27:40 +08:00
xream
7459821d84 fix: 修复 Egern VMess tcp 2025-02-11 23:56:45 +08:00
xream
71a26c0750 fix: 修复 mihomo 覆写配置无法使用普通脚本的问题 2025-02-11 13:18:42 +08:00
xream
012b580f5e feat: 调整 Egern VMess 传输层 2025-02-10 21:02:40 +08:00
xream
08e6835c2d feat: VMess/VLESS 校验 uuid 2025-02-10 13:34:58 +08:00
xream
8ecfeed953 feat: Shadowsocks URI 部分逻辑修正 2025-02-10 06:44:24 +08:00
xream
01f781a270 feat: Shadowsocks URI 支持 Shadow TLS plugin 2025-02-10 06:32:17 +08:00
xream
d3b282f864 feat: 同步配置逻辑优化 2025-02-09 20:58:27 +08:00
xream
28d930ad7e doc: README 2025-02-07 19:43:06 +08:00
xream
d4813ea124 feat: 调整 Egern VMess security 逻辑 2025-02-06 18:18:15 +08:00
xream
a18ba58860 feat: mihomo 和 Shadowrocket VMess cipher 支持 zero 2025-02-06 18:08:46 +08:00
xream
69d1f87249 feat: Proxy URI Scheme 支持省略端口号(http 默认为 80, tls 默认为 443) 2025-02-06 14:59:50 +08:00
xream
d786601e30 feat: Egern 正式支持 Shadowsocks 2022 2025-02-06 00:04:54 +08:00
xream
857e736274 feat: 支持 VLESS spx 参数; 支持 Trojan 结合 REALITY/XHTTP 2025-02-05 20:01:41 +08:00
xream
ea17ca6089 feat: Loon 正式支持 Shadow-TLS 2025-02-03 13:47:17 +08:00
xream
fe94b2e76d feat: mihomo snell 版本小于 3 的节点, 强制去除 udp 字段, 防止内核报错 2025-02-02 18:59:14 +08:00
xream
8228189476 feat: 去除订阅流量信息中空字段, 增强兼容性 2025-02-02 18:39:46 +08:00
xream
121a2d3c52 fix: 修复 Surge 输入的 tfo 2025-01-31 15:14:19 +08:00
xream
5b46a25448 fix: 修复 Egern 和 Stash 可根据 User-Agent 自动包含官方/商店版/未续费订阅不支持的协议 2025-01-26 20:41:57 +08:00
xream
8bb19debdc feat: Egern 增加 TUIC 2025-01-23 08:22:48 +08:00
xream
0292124f4b feat: 支持 VLESS XHTTP extra 2025-01-22 09:43:43 +08:00
xream
f35ddb8160 doc: README 2025-01-21 12:02:49 +08:00
xream
597062b1ac fix: 修复 Base64 合法性判断 2025-01-17 16:34:30 +08:00
xream
7dcfb22911 feat: Loon 节点支持 ip-mode 2025-01-15 23:54:35 +08:00
xream
c0e5ff2d48 fix: ss none 必须配置 password 2025-01-15 23:11:34 +08:00
xream
9e47b1baf6 fix: 修复组合订阅空 subscription-userinfo 的问题 2025-01-14 11:34:02 +08:00
xream
84875fdba5 fix: 修复组合订阅中的单条订阅透传 User-Agent 2025-01-14 08:25:53 +08:00
xream
eae9f0869c feat: 组合订阅中的单条订阅也支持透传 User-Agent 2025-01-13 20:09:00 +08:00
xream
fcef8e755e feat: Loon 排除 XTLS; 切换使用 esbuild 打包 2025-01-13 16:03:52 +08:00
xream
fc66309a0f ci: pnpm 2025-01-13 14:44:34 +08:00
xream
3a4de52c20 feat: Egern 和 Stash 可根据 User-Agent 自动包含官方/商店版/未续费订阅不支持的协议 2025-01-13 14:27:08 +08:00
xream
354c2c0b1b feat: Surge 默认开启 Shadowsocks 2022 2025-01-13 14:00:38 +08:00
xream
7403946d80 feat: Egern 使用 includeUnsupportedProxy 参数开启 Shadowsocks 2022 2025-01-12 06:12:14 +08:00
xream
39f7355e02 feat: Loon 使用 includeUnsupportedProxy 参数开启 Shadowsocks/ShadowsocksR + Shadow TLS 2025-01-11 23:34:00 +08:00
xream
f60731b36a feat: 日志中增加上传配置的响应 2025-01-09 18:56:36 +08:00
xream
6099c2bd97 ci: GitHub Action 2025-01-09 09:35:07 +08:00
xream
bf2e80cf30 fix: 修复 Loon Shadow TLS 2025-01-09 09:30:16 +08:00
xream
c4a682baba feat: gist 单页数量改为 100 2025-01-09 09:14:36 +08:00
xream
607420bb0d feat: Loon 使用 includeUnsupportedProxy 参数开启 Shadowsocks + Shadow TLS V3 2025-01-08 22:52:00 +08:00
xream
438a3a3db3 feat: Base64 Pre-processor 检测解码是否正常 2025-01-08 20:13:46 +08:00
xream
3eee8a5102 fix: 修复 Clash Pre-processor 2025-01-08 19:49:34 +08:00
xream
4ad7803511 feat: 文件支持 Mihomo 配置, 支持使用覆写; target 名称适配大小写和别名 2025-01-03 23:37:50 +08:00
xream
b40d312141 feat: Loon 正式支持 Shadowsocks 2022 2024-12-31 23:24:35 +08:00
xream
b053482435 doc: demo.js 2024-12-31 15:28:32 +08:00
xream
41e5964fa4 feat: sing-box 支持使用 _network 字段来设置 network 2024-12-31 15:27:14 +08:00
xream
ab4d9b4c7e fix: 修复代理 App 版中路由 target 参数为空的情况 2024-12-30 21:36:45 +08:00
xream
bf42d6ec02 feat: 组合订阅和文件的导出导入 2024-12-30 21:21:18 +08:00
xream
856b6c1be0 feat: 支持订阅级别的 noCache 2024-12-29 23:37:07 +08:00
xream
46f8296667 feat: 分享支持多一级路由指定输出目标 2024-12-29 22:08:24 +08:00
xream
75a53b23b8 feat: 订阅支持开关 passThroughUA 透传请求的 User-Agent 2024-12-29 21:33:15 +08:00
xream
1b08cb8ae1 feat: 支持订阅参数 url 同时支持单条本地节点内容, 支持多一级路由指定输出目标 2024-12-29 21:03:52 +08:00
xream
9e6a147703 feat: 支持 Egern 前置代理 prev_hop 和 Hysteria2 端口跳跃 2024-12-29 20:05:55 +08:00
xream
44ede4d7e7 feat: sing-box includeUnsupportedProxy 开启支持 Hysteria2 端口跳跃 2024-12-29 16:07:33 +08:00
xream
bd44e81ed9 feat: Egern 增加默认 sni 2024-12-28 21:00:40 +08:00
xream
4d51172919 feat: 下载订阅的日志中增加请求 target 和实际输出 2024-12-28 17:42:26 +08:00
xream
d51577aedb feat: Clash 订阅仅缓存 proxies 数据 2024-12-27 21:55:13 +08:00
xream
c1a7a313c7 feat: geo 更新 2024-12-27 21:35:51 +08:00
xream
e16f21b102 feat: geo 更新 2024-12-26 03:40:53 +08:00
xream
00bda4b257 feat: UDP 协议跳过设置 utls 2024-12-24 21:43:23 +08:00
xream
4cf920438b doc: demo.js 2024-12-24 20:49:41 +08:00
xream
e238a033dc doc: README 2024-12-24 15:10:38 +08:00
xream
221acdaf23 feat: Clash 系输入支持 mieru; 调整 juicity 和 mieru 相关过滤逻辑 2024-12-24 15:08:28 +08:00
xream
4331fd2138 feat: 组合订阅支持手动设置流量信息. 支持使用链接. 此时使用响应内容 2024-12-24 01:20:38 +08:00
xream
c4f82642b9 fix: 修复订阅不存在时不打印错误日志的问题 2024-12-23 14:14:10 +08:00
xream
9cf33887c6 feat: 手动设置流量信息时, 支持使用链接. 此时使用响应内容 2024-12-22 21:57:01 +08:00
xream
40e651d301 feat: sing-box 支持 detour 参数(之前只能用 underlying-proxy 或 dialer-proxy 来设置) 2024-12-22 20:06:00 +08:00
xream
1ff6de9979 fix: 修复 edns sourcePrefixLength 2024-12-21 21:13:09 +08:00
xream
5335fc17d8 feat: 支持 socks5, socks5+tls, http, https(便于输入) 格式输入 2024-12-16 21:06:46 +08:00
xream
451c6fa2ad feat: 支持 Shadowsocks 2022 的 URI 输入/输出 2024-12-15 23:03:41 +08:00
xream
11c9d20f65 fix: 修复 Surge SOCKS5 解析 2024-12-13 02:27:03 +08:00
xream
816de94599 feat: Mihomo 支持 direct 2024-12-12 18:35:55 +08:00
xream
190f358b8f feat: Surge 支持 direct 2024-12-12 18:22:25 +08:00
xream
145fa83224 feat: 处理状态码 2024-12-12 15:35:19 +08:00
xream
d659bdcfb5 fix: 修复 SS URI 解析 2024-12-08 11:24:46 +08:00
xream
7605ca39de feat: Loon 使用 includeUnsupportedProxy 参数开启 Shadowsocks 2022 2024-12-05 23:50:51 +08:00
xream
f3862eb962 feat: 优化去除无效节点逻辑 感谢群友 Cooip JM 2024-12-05 12:45:07 +08:00
xream
629f584e2f feat: gRPC 支持 authority 2024-12-05 00:52:11 +08:00
xream
074026a997 feat: 增加 subscription-userinfo 兼容性 2024-12-04 00:56:53 +08:00
xream
6ba6365969 feat: geo 更新 2024-11-30 13:58:04 +08:00
xream
5dcd708f9c feat: 支持禁用节点操作 2024-11-29 21:03:29 +08:00
xream
d15514f2df feat: VMess URI 输出支持 alterId; Trojan 支持 fp 和 alpn 2024-11-28 16:04:52 +08:00
xream
746c5975d7 feat: 找不到资源时不通知, 仅保留日志 2024-11-28 15:44:55 +08:00
xream
a8391ad8e6 feat: Trojan URI 支持省略端口号 2024-11-28 13:15:22 +08:00
xream
853c032872 feat: Stash 使用 includeUnsupportedProxy 参数开启 Shadowsocks 2022 2024-11-27 15:20:20 +08:00
xream
5d60afb957 ci: 去除 GitLab Sync 2024-11-26 15:01:04 +08:00
xream
95a0f45c34 feat: 默认缓存阈值 1024KB 2024-11-24 12:31:18 +08:00
xream
6e713f75f3 feat: 默认超时 8000ms 2024-11-24 12:13:52 +08:00
egerndaddy
66bc15cea5 Update Egern.yaml 2024-11-22 23:59:59 +08:00
xream
5026a74556 feat: 支持 Egern 输出 2024-11-21 13:16:04 +08:00
xream
6d7255dc05 feat: 默认输出格式改为 V2Ray; accept 为 application/json 时, 输出 JSON; 响应增加 X-Powered-By Sub-Store 2024-11-19 23:06:45 +08:00
xream
a10317844f feat: 手动设置的订阅流量信息会附加到订阅自己的流量信息之前 2024-11-17 23:40:03 +08:00
xream
a053f907fc feat: 默认查询流量信息的 User-AgentQuantumult%20X/1.0.30 (iPhone14,2; iOS 15.6) 改为 clash; 流量信息缓存逻辑调整 2024-11-17 02:10:38 +08:00
xream
58eaa91c66 feat: 支持解析订阅中的 profile-web-page-url 字段 2024-11-17 01:02:28 +08:00
xream
d5d001c109 feat: Surge 密码解析支持首尾成对的单引号双引号, 输出时增加双引号 2024-11-16 21:50:52 +08:00
xream
d92feb8c25 feat: 支持更多的 subscription-userinfo 2024-11-12 22:20:46 +08:00
xream
4dd42e8077 feat: 支持更多的 subscription-userinfo 2024-11-12 22:06:22 +08:00
xream
0d761c79ef feat: 订阅刷新按钮逻辑调整为无缓存刷新订阅和流量 2024-11-10 01:22:48 +08:00
xream
3ff2ad5873 feat: 模块版文件中增加 token 路由 2024-11-08 18:10:39 +08:00
xream
64d13d954d feat: 支持管理 token 2024-11-04 13:59:57 +08:00
xream
773cb16e91 fix: 修复 VMess URI SNI 2024-11-01 20:27:23 +08:00
xream
3bf81767c0 feat(wip): 支持自定义 share token 2024-10-31 23:33:34 +08:00
xream
57b9d97e1e feat: MMDB 加入 $utils.ipasn 2024-10-31 01:39:13 +08:00
xream
facfdac06f feat(wip): 支持 JWT 2024-10-31 00:23:45 +08:00
xream
e865459467 feat(wip): 支持 JWT 2024-10-30 23:08:01 +08:00
xream
340e20b318 feat(wip): 支持 JWT 2024-10-30 22:51:31 +08:00
xream
004e0a575d feat(wip): 支持 JWT 2024-10-30 22:27:39 +08:00
xream
241be53cb5 feat: cipher 应为小写 2024-10-30 16:07:27 +08:00
xream
314989aa55 feat: 远程订阅支持 insecure 不验证服务器证书 2024-10-30 14:33:34 +08:00
xream
a93f13d295 fix: 修复 surge mac 未开启 mihomo 时, 对于不支持的节点未报错, 导致出现 proxy 为 undefined 的问题 2024-10-29 18:31:02 +08:00
xream
2bb19ae8be feat: Surge 支持 udp-port, 修复 udp-relay 参数解析 2024-10-27 19:00:42 +08:00
xream
efd6e71c9b feat: 支持 VLESS mKcp 2024-10-23 17:38:59 +08:00
xream
d52d0f50f3 fix: 处理乱填的订阅流量信息解析报错 2024-10-19 19:12:25 +08:00
xream
d71d1296f8 fix: 修复 YAML 处理 undefined 的问题 2024-10-18 12:38:58 +08:00
xream
deaa1630a1 feat: ⚠️ BREAKING CHANG 仅手动指定 target 为 SurgeMac 时, 启用 mihomo 来支援 Surge 本身不支持的协议 2024-10-17 20:26:07 +08:00
xream
63814b3c3c fix: 修复 VMess VLESS servername 2024-10-17 14:01:44 +08:00
xream
d66f4d5e54 fix: 修复传输层和 SNI 的问题(有问题麻烦即时反馈 谢谢) 2024-10-16 21:31:41 +08:00
xream
21810be696 commit
feat: Surge 支持 Shadowsocks 2022(为了兼容 必须使用 `includeUnsupportedProxy` 参数或开启 `包含官方/商店版不支持的协议` 开关)
2024-10-15 17:00:13 +08:00
xream
a1a522c472 doc: README 2024-10-12 14:51:58 +08:00
xream
62295895a9 fix: 修复组合订阅预览 2024-10-12 10:46:39 +08:00
xream
ed5b395afc feat: 组合订阅支持通过单条订阅的标签进行关联 2024-10-11 20:57:45 +08:00
xream
8219a09f53 chore: bump release version 2024-10-11 20:00:09 +08:00
pillarcoin
b0117ca82d fix: clash 配置中 VLESS 节点的 short-id 值被错误解析 2024-10-11 19:10:02 +08:00
xream
168c7f597a feat: 文件支持设置代理/策略, 链接支持传入 proxy 参数指定代理/策略; 修复代理/策略优先级 2024-10-07 22:05:07 +08:00
xream
69a6480477 feat: 组合订阅支持设置代理/策略, 链接支持传入 proxy 参数指定代理/策略 2024-10-07 20:56:33 +08:00
xream
ac75b9e78b feat: Node.js 支持使用环境变量 SUB_STORE_BACKEND_DEFAULT_PROXY 设置默认代理; ProxyUtils 增加 download 方法 2024-10-07 18:43:29 +08:00
xream
6d86235040 feat: 流量和同步配置也使用默认代理/策略 2024-10-07 18:34:39 +08:00
xream
4b307067d0 feat: 新增全局代理/策略设置, 前端 > 2.14.265 2024-10-07 18:05:06 +08:00
xream
d7e5a133c9 feat(file): 新增启用下载(文件名为显示名称), 前端 > 2.14.264 2024-10-07 17:26:15 +08:00
xream
7c4b8fd8a5 doc: demo.js 2024-09-22 06:02:58 +08:00
xream
b4ec6cfd0d feat: 增加 _subDisplayName _collectionDisplayName 2024-09-18 19:42:53 +08:00
xream
94a609f08e doc: demo.js 2024-09-18 19:22:12 +08:00
xream
ebcf1265e6 feat: 正式弃用旧的 subName 和 collectionName 2024-09-18 19:18:50 +08:00
xream
abf3c84cd4 fix: 修复 password 为数字时的 bug 2024-09-16 01:43:16 +08:00
xream
c63d9a304e feat: 使用自定义缓存时 cacheKey 的值不能为空 2024-09-13 23:42:55 +08:00
xream
4a96716cd9 feat: 内置的 Google/Cloudflare DNS 更换为 DoH 2024-09-09 14:56:47 +08:00
xream
0b67abf4f6 chroe: 修改脚本链接为 release 分支 2024-09-07 23:21:42 +08:00
xream
2aa4308c83 fix: 传输层 path 应为以 / 开头的字符串 2024-09-05 17:39:42 +08:00
xream
82a762b0f6 fix: 传输层 path 应以 / 开头 2024-09-05 17:15:30 +08:00
xream
21f6f7721c feat: 处理非字符串的 ports 字段 2024-09-04 13:40:17 +08:00
xream
93f3ab7f44 doc: README 2024-09-04 10:49:52 +08:00
xream
57d7d98507 feat: Node.js 版支持环境变量 SUB_STORE_BACKEND_DOWNLOAD_CRON 设置定时恢复配置, SUB_STORE_BACKEND_UPLOAD_CRON 设置定时备份配置, SUB_STORE_BACKEND_SYNC_CRON 设置定时同步订阅/文件 2024-09-04 02:20:28 +08:00
xream
80880f066e feat: SurgeMac 支持使用 mihomo 来支援 Surge 本身不支持的协议; 弃用旧的 ssr-local 方案 2024-09-03 20:31:42 +08:00
xream
8781f476cc feat: 订阅和文件的请求链接支持传入 $options , 可在脚本中使用 2024-09-03 13:58:10 +08:00
xream
7217055c15 feat: sing-box VLESS Reality uTLS 默认启用 2024-09-02 21:20:22 +08:00
xream
18834895d8 feat: Surge Hysteria2 与 TUIC 协议支持端口跳跃; Hysteria2 URI 的端口部分支持 端口跳跃 的「多端口地址格式」 2024-09-02 16:38:21 +08:00
xream
7f6d132eb9 feat: 脚本操作传入上下文 require (仅对应的环境支持)" 2024-08-31 22:39:54 +08:00
egerndaddy
48e8c2a8af doc: 添加 Egern 模块链接 2024-08-29 13:26:27 +08:00
egerndaddy
d5a0c8839a Create Egern.yaml 2024-08-29 13:09:59 +08:00
xream
05f0dbedcf fix: DoH 结果过滤 2024-08-29 12:30:49 +08:00
xream
11db3cfdac fix: 修复 SurgeMac ShadowsocksR obfs-param 2024-08-28 14:51:06 +08:00
xream
5fce41347a feat: Loon SOCKS5 UDP 2024-08-26 00:33:22 +08:00
xream
169bd88bef feat(uri): VMess URI 输入支持 allowInsecure(输出不支持, 与 2dust/v2rayN 分享链接逻辑一致) 2024-08-18 15:53:13 +08:00
xream
d6e86a3176 feat(geo): 增加 利雅得 Riyadh 2024-08-17 14:06:28 +08:00
xream
5bcf5d557a feat: 支持 Loon tls-pubkey-sha256, tls-cert-sha256 2024-07-30 22:17:25 +08:00
xream
4a2211becd chore: 传输层应该有配置, 暂时不考虑兼容不给配置的节点 2024-07-24 11:27:33 +08:00
xream
45f00fc002 chore: 增加部分 clash.meta(mihomo) 内核客户端的 User-Agent(clash-verge, flclash) 2024-07-20 14:48:39 +08:00
xream
1da129a76c feat: 处理端口跳跃(感谢亚托莉佬) 2024-07-19 15:23:44 +08:00
xream
520e3f9c15 fix: 修复 URI 报错 2024-07-19 14:33:34 +08:00
xream
6f18dae272 feat: 处理不规范的 hysteria2 节点 2024-07-19 09:45:28 +08:00
xream
b08f6ea13b fix: 被识别为 IP4P 的域名解析结果均增加 _IP4P 字段; 修复报错 2024-07-18 19:48:01 +08:00
xream
38cc9a5a47 fix: 域名解析修复; 结果增加 _IP4P 字段 2024-07-18 19:42:57 +08:00
xream
0f94d4e964 feat: IP4P 合并进 IPv6; ProxyUtils 中增加 ipAddress 2024-07-18 18:35:22 +08:00
xream
29c02277bf chore: bump release version 2024-07-08 02:51:21 +08:00
xream
3717488095 Merge pull request #339 from zhiqiang02/add-tai-wan-keyword
Add 'Tai Wan' as a keyword for Taiwan flag
2024-07-08 02:50:13 +08:00
zhiqiang02
9a2216fdd1 Add 'Tai Wan' as a keyword for Taiwan flag
碰到了某机场奇奇怪怪的节点名
2024-07-08 02:38:50 +08:00
xream
4a2815125b chore: bump release version 2024-07-02 21:04:14 +08:00
xream
50e4dbc53e Merge pull request #336 from cooip-jm/patch-1
处理grpc-opts为 {} 的情况
2024-07-02 21:03:30 +08:00
cooip-jm
aa68018ad0 处理grpc-opts为 {} 的情况
该字段仅影响sing-box内核,对mihomo无影响
2024-07-02 21:01:11 +08:00
xream
cc4d862335 feat: 处理 reality-opts 为 {} 的情况 2024-07-02 20:39:04 +08:00
xream
adeff19c4d feat: 文件支持设置 查询流量信息订阅链接. 服务器版中使用此链接可在响应中传递订阅流量信息 2024-06-28 18:34:26 +08:00
xream
08aaad3bf5 feat: 域名解析支持自定义 EDNS(需新版前端) 2024-06-22 11:45:37 +08:00
xream
0e8328dc10 feat: 域名解析支持自定义 DoH(需新版前端) 2024-06-20 21:42:15 +08:00
xream
bc2afca4ff feat: 域名解析增加超时参数(默认使用全局超时) 2024-06-20 13:41:05 +08:00
xream
9250e90848 doc: demo.js 增加更多字段的说明 2024-06-20 11:44:07 +08:00
xream
c45310e731 feat: 域名解析新增 _resolved_ips 为解析出的所有 IP 2024-06-20 11:28:17 +08:00
xream
b90c566380 fix: 修复 Tencent DNS 缓存 2024-06-20 10:59:06 +08:00
xream
26e9b1b6ef fix: 修复 Loon Hysteria2 salamander 混淆 2024-06-17 11:08:43 +08:00
xream
1d0ff6473a feat: 支持 Loon Hysteria2 salamander 混淆 2024-06-16 21:49:13 +08:00
xream
8c1d478941 feat: 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint 2024-06-13 20:44:12 +08:00
xream
f24bb15394 feat: Stash 支持 juicity, ssh 2024-06-12 15:16:56 +08:00
xream
56d7c01b8b fix: sing-box tls cert 应该为数组 2024-06-10 19:10:57 +08:00
xream
b2b187f8e8 fix: tlsParser typo 2024-06-10 19:07:19 +08:00
xream
76da8d1c5c feat: /api/sub/flow/:name 接口支持指定远程订阅 url(可携带订阅 url 支持的参数, 例如 flowUserAgent) 2024-06-10 13:24:06 +08:00
xream
d7f7069ee0 fix: 兼容空的订阅链接 2024-06-09 01:42:40 +08:00
xream
0aef932843 chore: demo.js 2024-06-06 21:50:13 +08:00
xream
78d9ffa290 feat: VMess URI 支持 TCP/H2 传输层 2024-06-03 21:14:07 +08:00
xream
ba13620701 feat: ws, http, h2 传输层补全 path 2024-06-03 00:34:03 +08:00
xream
6538205956 feat: ProxyUtils.removeFlag 2024-06-02 18:30:53 +08:00
xream
4424886899 feat: 支持 flowUrl 2024-06-02 16:03:01 +08:00
xream
aefc05b0d8 feat: 进一步优化乐观缓存和同步配置的逻辑 2024-06-01 20:09:57 +08:00
xream
fedf0e5587 feat: 进一步优化乐观缓存和同步配置的逻辑 2024-06-01 19:50:16 +08:00
xream
96e2152cec fix: 简单修复乐观缓存(当异步更新乐观缓存时, 若存在常规缓存, 将使用常规缓存) 2024-05-31 20:52:01 +08:00
xream
2825f13186 chore: 文案 2024-05-31 11:42:23 +08:00
xream
7a8be56cd8 doc: README 2024-05-30 21:32:48 +08:00
xream
707463e09e feat: Surge includeUnsupportedProxy 去除 HTTP 传输层(不一定能通, 由服务端配置确定) 2024-05-30 18:41:56 +08:00
xream
befe20c773 feat: GUIforCores 请求增加参数 proxy, timeout 2024-05-30 17:19:38 +08:00
xream
4c65f261ba fix: VMess URI 输入传输层为 HTTP 时, path 默认为 / 2024-05-30 14:28:02 +08:00
xream
78ac733ed9 fix: 乐观缓存未捕获错误 2024-05-30 13:10:08 +08:00
xream
8d1150e48d feat: 节点上的额外参数调整为下划线开头, 原参数目前仍保留, 若有脚本需要使用这些参数请尽快修改(_subName, _collectionName, _resolved, _no-resolve) 2024-05-30 04:48:13 +08:00
xream
17fd3c1dcc feat: 节点上的额外参数调整为下划线开头, 原参数目前仍保留, 若有脚本需要使用这些参数请尽快修改(_subName, _collectionName, _resolved) 2024-05-30 04:28:54 +08:00
xream
21f728f324 chore: 去除 Surge/Surfboard 输出节点名中的逗号和等号 2024-05-29 19:15:52 +08:00
xream
09cd3c9ff7 feat: 新增 定时处理订阅 功能, 避免 App 内拉取超时 2024-05-28 12:05:35 +08:00
xream
e718e4871e feat: Surge Beta 模块支持定时处理订阅. 一般用于定时处理耗时较长的订阅, 以更新缓存. 这样 Surge 中拉取时就能用到缓存, 不至于总是超时 2024-05-28 02:31:25 +08:00
xream
891e9253ea fix: Base64 输入支持 hy2:// 2024-05-26 10:28:46 +08:00
xream
591e3881d5 fix: Stash 服务器证书 SHA256 指纹字段为 server-cert-fingerprint 2024-05-25 18:54:51 +08:00
xream
648717c0e3 feat: 下载订阅日志中增加请求的 User-Agent 2024-05-25 18:29:48 +08:00
xream
69fa1978be fix: Stash 服务器证书 SHA256 指纹字段为 server-cert-fingerprint 2024-05-21 11:05:45 +08:00
xream
45a710490e chore: demo.js 2024-05-20 17:42:09 +08:00
xream
ddd7753bf6 chore: demo.js 2024-05-20 17:38:49 +08:00
xream
16f3ab7272 chore: 最新版 Surge 已删除 ability: http-client-policy 参数, 模块暂不做修改, 对测落地功能无影响 2024-05-18 23:57:18 +08:00
xream
fff63928c9 feat: 某些域名仅支持从国内 DNS 解析正确结果, 为方便部署在海外的用户, 使用国内 DNS 解析时, ECS IP 指定为国内 IP 2024-05-18 22:24:58 +08:00
xream
79748c7042 chore: 文案 2024-05-17 17:49:10 +08:00
xream
b30e5b4f71 doc: Shadowrocket 模块 2024-05-15 20:58:55 +08:00
xream
87959922be chore: bump release version 2024-05-14 20:45:39 +08:00
xream
34122f3971 Merge pull request #322 from onejibang/master
chore: Change network request method
2024-05-14 20:45:00 +08:00
onejibang
d64e547c68 chore: Change network request method 2024-05-14 16:51:05 +08:00
xream
aaa073745b chore: bump release version 2024-05-13 19:44:27 +08:00
xream
e6e86423aa Merge pull request #320 from onejibang/feature-gui-for-cores
feat: 适配GUI.for.Cores项目组下的客户端程序
2024-05-13 19:41:45 +08:00
xream
66d0a6fd4c Merge branch 'master' into feature-gui-for-cores 2024-05-13 19:41:28 +08:00
onejibang
9c8cdb2ab0 chore: Canonical variable name 2024-05-13 19:22:33 +08:00
onejibang
2a1c88c496 feat: support HEAD method 2024-05-13 19:12:22 +08:00
xream
1092d49d90 feat: 使用了自定义缓存 cacheKey 的远程订阅 调整为乐观缓存 2024-05-13 17:08:17 +08:00
onejibang
cbda600c32 feat: Provide virtual disk operation API 2024-05-13 16:35:05 +08:00
xream
0f1a65cc42 feat: Node.js 版支持 MMDB, 通过环境变量或在脚本中传入数据库文件路径, 可使用 ipaso 和 geoip 方法 2024-05-12 23:17:11 +08:00
onejibang
749dd59b43 Adapted for GUI.for.Cores 2024-05-11 17:32:01 +08:00
xream
640c194220 feat: 支持 Trojan, VMess, VLESS httpupgrade(暂不支持 Shadowsocks v2ray-plugin) 2024-05-10 10:15:11 +08:00
Peng-YM
df502e6206 Update README.md 2024-05-09 09:38:36 +08:00
xream
5ac4aa8cb4 fix: 简单修复 SS URI 多参数拼接 2024-05-05 02:14:55 +08:00
xream
39ac327444 feat: 当无插件参数时, 去除 SS URI 输出中的 / 以兼容部分客户端 2024-05-05 02:11:50 +08:00
xream
d43ffe29f8 feat: ProxyUtils 中增加 Gist 类; 补充 demo.js 中的示例 2024-05-04 21:35:27 +08:00
xream
2a69fc3acd fix: 兼容不规范的 QX URI 2024-05-03 03:56:59 +08:00
xream
22ad63f3f1 fix: 哪吒探针在线时长 2024-04-30 15:39:14 +08:00
xream
73ac2e85d6 feat: 全部是 WireGuard 节点的订阅, 支持输出为 Surge 模块 2024-04-25 16:55:32 +08:00
xream
af263bbd0a fix: 旗帜操作中将 🏴‍☠️ 🏳️‍🌈 也视为已有旗帜并在删除后添加新旗帜 2024-04-25 13:38:01 +08:00
xream
684cf2f52d fix: 处理手动删除 Gist 之后, Sub-Store 侧重新同步的逻辑 2024-04-23 09:28:39 +08:00
xream
83eb46f455 feat: 支持完整导出和导入 Sub-Store 单条订阅数据 2024-04-22 15:15:57 +08:00
xream
394bc2428a Merge pull request #314 from eric-gitta-moore/master
feat: script ip-flag for node
2024-04-22 13:09:33 +08:00
Eric Moore
5cca33d49b feat: script ip-flag for node 2024-04-22 12:59:06 +08:00
xream
5ec36f082c ci: restore the functionality of generating conventional changelog 2024-04-22 04:25:19 +08:00
xream
878354c44c fix: 修复对不规范的节点名称的处理 2024-04-22 02:51:44 +08:00
xream
e4ccb549c5 feat: QX 输出正式支持 VLESS 2024-04-22 02:15:54 +08:00
xream
496aa42e86 feat: 支持 Loon SOCKS5/SOCKS5-TLS 2024-04-21 12:36:11 +08:00
xream
83275bbc59 doc: README 2024-04-21 11:32:23 +08:00
xream
2e5f719762 chore: 增加探针版本(没有自定义的必要吧 默认为 0.0.1) 2024-04-20 07:45:49 +08:00
xream
ac1a46e853 feat: 哪吒探针网络监控接口支持用参数传入检测次数; 节点字段上自定义的多个次数, 只取最大值 2024-04-19 06:27:45 +08:00
xream
225a3373f2 feat: 哪吒探针网络监控接口提示不兼容的节点, 支持传入节点名 2024-04-19 05:57:26 +08:00
xream
8accee4084 feat: 订阅兼容哪吒探针网络监控接口(Loon/Surge 可输出节点延迟) 2024-04-19 05:16:24 +08:00
xream
f732add651 fix: sing-box wireguard 2024-04-17 11:36:23 +08:00
xream
cd48bce840 feat: GEO 增加 TYO 2024-04-17 05:50:50 +08:00
xream
d5f3f7f10e ci: GitHub Actions: Transitioning from Node 16 to Node 20 2024-04-17 01:12:07 +08:00
xream
053327bf93 feat: 支持 WireGuard URI 输入和输出 2024-04-17 00:56:53 +08:00
xream
9846c262c7 feat: 支持 dialer-proxy, detour 2024-04-14 21:34:45 +08:00
xream
dd3cf82660 feat: fancy-characters 增加 modifier-letter(小写没有 q, 用 ᵠ 替代. 大写缺的太多, 用小写替代) 2024-04-12 22:39:59 +08:00
xream
c977ebaa25 feat: 支持设置并在远程订阅失败时读取最近一次成功的缓存 2024-04-09 20:49:42 +08:00
xream
cb1a1216b5 chore: 处理订阅输出哪吒探针兼容响应的 Uptime 字段 2024-04-09 13:48:33 +08:00
xream
1d5c21839c fix: 修复 sing-box wireguard reserved 2024-04-07 19:48:08 +08:00
xream
07f0ea81ff chore: Surge 模块文案 2024-04-07 16:40:09 +08:00
xream
70f3d4c341 chore: Surge 默认模块更新为支持编辑参数的版本 2024-04-06 19:40:30 +08:00
xream
8ff99c314c doc: demo.js 2024-04-05 13:46:07 +08:00
xream
563cae90dd feat: 订阅支持输出哪吒探针兼容响应; 清理输出数据; 增加内部数据字段 2024-04-05 13:37:15 +08:00
xream
6081c185b4 fix: 修复 SS URI 解析错误 2024-04-04 16:52:46 +08:00
xream
26325d89c9 fix: 修复 Tencent DNS 解析 2024-04-04 15:26:19 +08:00
xream
cbb0424bc3 feat: GEO 增加 AMS 2024-04-03 00:45:48 +08:00
xream
ec96b1c910 feat: GEO 增加 JNB, SJC, SEL 2024-04-02 21:06:56 +08:00
xream
6a5b483159 fix: 修复内蒙古识别为蒙古的问题 2024-04-02 20:56:07 +08:00
xream
7fcd65bc6b feat: 区域过滤增加韩国德国 2024-04-02 20:40:13 +08:00
xream
25ad2847c3 chore: 日志输出增加订阅的来源 User-Agent 2024-03-31 10:59:49 +08:00
xream
e12234e33c feat: sing-box 订阅格式修改(如需原始格式 请使用 target=sing-box&produceType=internal); 清理 Clash 系无效字段 2024-03-31 09:36:32 +08:00
xream
021425c3b9 feat: ProxyUtils 增加 getFlag, getISO 方法 2024-03-31 08:20:51 +08:00
xream
ae1dcd4372 chore: geo 增加关键词 德意志 2024-03-31 06:59:31 +08:00
xream
6e94693ed2 feat: JSON 输出支持 produceType internal 2024-03-31 04:45:57 +08:00
xream
43933d5c03 feat: 处理 sni off 的情况. 若出现问题, 麻烦大家及时反馈 2024-03-30 01:12:34 +08:00
xream
ffe0de5add fix: 修复组合订阅子订阅失败导致预览失败 2024-03-26 14:59:24 +08:00
xream
054ea43c28 feat: 流量单位显示由 EB 提升到 YB 2024-03-25 03:50:47 +08:00
xream
50b19026c9 chore: 调整部分日志 2024-03-25 02:47:06 +08:00
xream
64548f6e83 feat: 处理传入 httpClient 的 timeout 参数 2024-03-24 07:28:16 +08:00
xream
51e1596ab0 feat: hysteria2 支持 mport, clash.meta(mihomo) 支持 ports 2024-03-21 20:07:24 +08:00
xream
1fa6af63c5 feat: 刷新时 清除所有缓存 2024-03-21 02:37:21 +08:00
xream
5b87a7bbe0 doc: demo.js 2024-03-21 01:52:42 +08:00
xream
79308ac256 chore: Sub-Store Simple 脚本增加脚本超时(默认 120) 可能会影响某些逻辑 待观察 2024-03-19 23:48:26 +08:00
xream
9003ab1f10 chore: 增加 gist 错误日志 2024-03-19 21:30:52 +08:00
xream
99b39a9e2b doc: demo.js 2024-03-18 20:24:18 +08:00
xream
1c1f009214 chore: 增加上传同步配置的详细日志 2024-03-16 02:47:01 +08:00
xream
65c0b83982 feat: 规则中处理 GEOIP/GEOSITE, Loon 已支持 SRC-PORT/DEST-PORT/PROTOCOL 2024-03-15 08:49:36 +08:00
xream
319ae7e837 release: backend version 2.14.256 2024-03-15 08:12:06 +08:00
xream
92a6775cdf Merge pull request #295 from makaspacex/master
[Feat]规则转换增加对GEOIP与GEOSITE的支持
2024-03-15 08:10:30 +08:00
makabaka
69c3182311 规则转换增加对GEOIP与GEOSITE的支持 2024-03-14 22:07:45 +08:00
xream
28dddc1d28 feat: 支持设置查询远程订阅流量信息时的 User-Agent 2024-03-14 19:45:39 +08:00
xream
8e1583742d feat: 通过代理/节点/策略获取订阅 现已支持 Surge, Loon, Stash, Shadowrocket, QX, Node.js 2024-03-14 01:54:07 +08:00
xream
66d2f65261 feat: SSH 协议跟进 clash.meta(mihomo) 的修改 2024-03-13 16:24:30 +08:00
xream
bcb8bd2882 feat: 支持使用代理/节点/策略获取订阅 2024-03-13 05:33:52 +08:00
xream
d357ca4990 feat: 支持自定义订阅流量信息 2024-03-12 01:17:56 +08:00
xream
94970b6922 fix: 修复订阅流量传递 2024-03-12 00:55:30 +08:00
xream
33de07268c fix: 修复重置天数微妙的偏差 2024-03-11 19:33:51 +08:00
xream
3b0dd362ce feat: 订阅支持开始日期和重置周期 2024-03-11 13:39:52 +08:00
xream
b57c1999e7 feat: 订阅支持每月重置天数 2024-03-10 23:08:56 +08:00
xream
ddf8cf5539 feat: produceArtifact 方法支持传入自定义 subscription; VLESS 非 reality 删除空 flow 2024-03-10 17:22:25 +08:00
xream
1c91fa05ac feat: SSH 新增 clash.meta(mihomo), 调整 Surge 和 sing-box 2024-03-08 19:01:01 +08:00
xream
0ccd0e5451 feat: 订阅列表的流量信息兼容远程和本地合并的情况, 排除设置了不查询订阅信息的链接 2024-03-08 18:40:44 +08:00
xream
17028f615b feat: Node.js 版 /api/utils/env 增加 meta 信息 2024-03-08 14:20:55 +08:00
xream
3cd103eb4b feat: OpenAPI 增加 isEgern, isLanceX; /api/utils/env 增加 meta 信息 2024-03-08 13:56:59 +08:00
xream
2a8483f22f fix: 修复 Surge WireGuard allowed-ips 双引号 2024-03-07 17:24:49 +08:00
xream
4636012f19 feat: 增加下载缓存阈值 2024-03-05 05:03:17 +08:00
xream
47a38805b2 feat: 域名解析新增 IP4P, 支持禁用缓存 2024-03-05 01:01:46 +08:00
xream
26d0efa4a5 feat: Stash/clash.meta(mihomo) 支持 interface-name 字段 2024-03-04 11:43:07 +08:00
xream
4abb9d7444 fix: 修复 Clash 节点名为 binary 的情况 2024-03-03 14:33:49 +08:00
xream
14bc4334b4 ci: git push assets to "release" branch 2024-02-28 23:07:16 +08:00
xream
ebec33d149 feat: 调整规则参数 2024-02-28 22:36:22 +08:00
xream
b5256fc950 Merge pull request #289 from makaspacex/master
修复IP-CIDR的option错误
2024-02-28 22:19:11 +08:00
makabaka
0fb2a78474 修复IP-CIDR的option错误 2024-02-28 20:06:03 +08:00
xream
ef3cedebd7 doc: README 2024-02-23 22:03:10 +08:00
xream
06aa928d03 fix: 参数对象 2024-02-23 20:15:37 +08:00
xream
4ed88777f2 chore: 调整默认定时为每天 23 点 55 分 2024-02-23 20:05:48 +08:00
xream
87595c31b1 fix: Surge TUIC server-cert-fingerprint-sha256 2024-02-22 11:37:18 +08:00
xream
44fa0f761a fix: sing-box wireguard reserved 2024-02-21 19:09:23 +08:00
xream
236a588fb4 feat: Surge 参数 tos allow-other-interface interface test-udp test-timeout hybrid; Stash 参数 benchmark-timeout; Surge 新增 SSH(仅密码方式), sing-box 新增 SSH 2024-02-18 05:25:31 +08:00
xream
6c4b2b5484 feat: 节点名称为空时, 添加默认节点名称 2024-02-17 17:41:54 +08:00
xream
e3c1533d40 chore: sing-box grpc servicename 应为字符串 2024-02-17 12:03:25 +08:00
xream
4a070a72b0 fix: 兼容不规范的 VLESS URI 2024-02-17 11:39:04 +08:00
xream
220ab78b5b fix: 兼容不规范的 VLESS URI 2024-02-17 10:50:33 +08:00
xream
d42722bd8e feat: 旗帜操作(支持更多选项) 2024-02-17 08:32:37 +08:00
xream
692232fd55 feat: 脚本操作完整支持 /api/file/name 的内部文件调用路径 2024-02-15 03:30:53 +08:00
xream
12c48cb613 feat: 同步配置前, 预处理订阅, 防止同时请求过多 2024-02-14 19:51:44 +08:00
xream
ddc832842c doc: 补充文档 2024-02-13 15:30:13 +08:00
xream
73e95302ce feat: 带参数 includeUnsupportedProxy 时, 支持输出 Surge WireGuard Section 2024-02-13 15:21:29 +08:00
xream
6ecf84c6cc chore: dev 调整 2024-02-11 01:10:01 +08:00
xream
bdc127c5e7 fix: 兼容更多 Trojan URI 格式 2024-02-08 23:06:39 +08:00
xream
c5721618df fix: 修复 Loon UDP 参数 2024-02-07 15:14:24 +08:00
xream
b3a2c52e7c fix: 缓存不合法时即刻重置 2024-02-07 01:14:52 +08:00
xream
8d2340b63f fix: 修复 Surge 协议 test-url 字段 2024-02-06 22:59:54 +08:00
xream
26a094e525 feat: Loon 解析器支持参数 ua=clash.meta&timeout=3000, 支持从链接重新获取 2024-02-06 21:29:10 +08:00
xream
b877529657 fix: 修复 PassWall VLESS URI 兼容性 2024-02-06 00:30:53 +08:00
xream
33d39bdef3 fix: 修复域名解析 2024-02-05 13:19:22 +08:00
xream
9772be82e7 chore: Stash 覆写增加图标 2024-02-05 11:40:02 +08:00
xream
1ae00b2305 fix: 修复过滤非法节点功能 2024-02-04 01:22:27 +08:00
xream
e3aade7167 chore: 端口号允许为 0 2024-02-03 23:59:26 +08:00
xream
5f5219fd31 chore: 在 bundle 文件顶部添加版本号 2024-02-03 22:34:58 +08:00
xream
409b54bc70 chore: 开发流程使用 esbuild 2024-02-03 21:30:27 +08:00
xream
63cad8b378 feat: 支持 Hysteria(v1) URI 2024-02-03 17:54:37 +08:00
xream
28b53e1240 feat: 支持 TUIC v5 URI 2024-02-03 16:17:26 +08:00
xream
f8df06d4a5 feat: 支持 Loon fast-open 2024-02-02 22:55:00 +08:00
xream
ab6f6f612d chore: 增加 esbuild bundle(暂不启用 仅本地使用) 2024-02-02 19:53:14 +08:00
xream
f1739c3127 feat: 更新 Surge, Loon, QX 输入的 Shadowsocks cipher 2024-02-02 13:23:59 +08:00
xream
85bfc1a434 feat: 输出时校验 Surge, Surfboard, Loon, QX Shadowsocks cipher 2024-02-02 13:04:32 +08:00
xream
a9e9a4a933 chore: VLESS HTTP 传输层增加默认 path 2024-02-02 12:44:08 +08:00
xream
e23bd3e8fe chore: 脚本说明 2024-02-02 11:58:01 +08:00
xream
7ebbb43a75 chore: 增加脚本说明 2024-02-01 21:59:24 +08:00
xream
1bb7da4ec6 chore: 增加脚本说明 2024-02-01 21:58:30 +08:00
xream
68086275f7 chore: 增加脚本说明 2024-02-01 21:58:17 +08:00
xream
52d10bf4e6 chore: YAML 解析兼容(保持类型) 2024-01-31 03:27:07 +08:00
xream
54bfe655a4 chore: Clash 系输出中 tls 字段存在且不为布尔值时, 删除该字段防止客户端解析报错 2024-01-30 23:32:39 +08:00
xream
5b587e425c chore: YAML 解析兼容 2024-01-30 22:23:57 +08:00
xream
0274fa2300 feat: 远程订阅 URL 新增参数 validCheck 将检查订阅有效期和剩余流量 2024-01-30 14:14:57 +08:00
xream
d2ef060d4c fix: v2ray-plugin 2024-01-30 03:24:42 +08:00
xream
465f3e5cdc feat: h2 传输层修正 host 为数组, path 不为数组 2024-01-30 02:17:58 +08:00
xream
3cebb6e3f3 feat: 支持 tls 的 plugin 中跳过证书验证; Surge Shadow TLS 默认版本为 2; sing-box 支持 Shadowsocks Shadow TLS 2024-01-30 01:53:22 +08:00
xream
216313f9c1 feat: 支持更多不规范的 SS URI; 去除 Surfboard 节点名中的等号; 支持 Mihomo shadowsocks shadow-tls 2024-01-30 00:49:41 +08:00
xream
158a50435f feat: 支持更多不规范的 VLESS/SS URI 2024-01-29 23:38:21 +08:00
xream
4ce70db88a chore: 增加定时任务默认为每天 0 点的说明 2024-01-24 15:17:45 +08:00
xream
a175ab4802 chore: Beta 模块, 支持最新 Surge iOS TestFlight 版本的分类,参数设置和参数说明(增加双引号 确保兼容性) 2024-01-23 12:45:02 +08:00
xream
155ceb27ab chore: Beta 模块, 支持最新 Surge iOS TestFlight 版本的分类,参数设置和参数说明 2024-01-23 04:03:35 +08:00
xream
fe0d5bb0d6 chore: 新增 Beta 模块, 支持最新 Surge iOS TestFlight 版本的分类和参数设置 2024-01-22 04:48:34 +08:00
xream
7dbbbe9af2 feat: 优化流量解析规则 2024-01-20 23:00:37 +08:00
xream
190a8b6fc3 Merge pull request #275 from dnomd343/master
fix: 流量信息匹配错误
2024-01-20 22:40:13 +08:00
xream
c39930707b chore: 脚本操作时不使用空值合并运算符 2024-01-20 22:14:23 +08:00
Dnomd343
7045cbe5c8 fix: scientific counting matching error 2024-01-20 13:16:11 +08:00
xream
319525d4a7 feat: 优化调整 Gist 同步逻辑; 增加 GitLab Snippet 同步 2024-01-20 05:33:31 +08:00
xream
8689b08136 fix: 配置接口补齐错误处理 2024-01-20 00:50:35 +08:00
xream
b463299e76 feat: 域名解析支持类型和过滤 2024-01-19 21:43:54 +08:00
xream
aaa1832145 release: backend version 2.14.184 2024-01-19 12:54:40 +08:00
xream
82dfb79c26 Merge pull request #274 from izhangxm/feat_add_proxy_convter_api
增加规则转换与协议转换API接口
2024-01-19 12:35:32 +08:00
makabaka
00b12d5775 增加规则转换与协议转换API接口 2024-01-19 12:23:04 +08:00
xream
a4f30cced6 feat: 解析并删除旧的 ws-path ws-headers 字段 2024-01-19 10:18:27 +08:00
xream
486d6a4d3e release: backend version 2.14.182 2024-01-18 17:17:15 +08:00
xream
9da9419155 Merge pull request #273 from izhangxm/master
修复clash规则头部有注释的情况下规则转换功能失败的问题
2024-01-18 17:07:59 +08:00
makabaka
b20effb4ec fix_clashprovider_test 2024-01-18 15:09:24 +08:00
xream
23a0857bcf feat: Clash 节点支持 fingerprint(内部转为 tls-fingerprint); 支持 Clash 配置文件中的 global-client-fingerprint 优先级低于 proxy 内的 client-fingerprint 2024-01-18 12:14:35 +08:00
xream
f45faa7763 Revert "fix: sing-box fingerprint"
This reverts commit 42404537e8.
2024-01-18 11:36:07 +08:00
xream
1fa518ba9b fix: sing-box fingerprint 2024-01-18 11:29:15 +08:00
xream
bd66596647 feat: 支持同步配置时选择包含官方/商店版不支持的协议; 同步配置优化 2024-01-18 06:18:05 +08:00
xream
dc57e0886a feat: 兼容不规范的 VLESS URI 2024-01-18 01:17:06 +08:00
xream
544d8d907d revert: 去除 Loon Trojan HTTP 传输层 2024-01-17 22:13:54 +08:00
xream
544f8de9c6 chore: README 2024-01-17 21:55:56 +08:00
xream
0b18118e60 chore: README 2024-01-17 21:49:56 +08:00
xream
eee68fd024 fix: Loon 传输层 2024-01-17 21:24:17 +08:00
xream
13db14b703 feat: 支持 QX VLESS 输出(不支持 XTLS/REALITY) 2024-01-17 21:16:34 +08:00
xream
2c54275ea3 feat: produceArtifact 支持 Stash internal (Fixes #271) 2024-01-17 20:31:43 +08:00
xream
4bc372c6fc chore: README 2024-01-17 19:55:22 +08:00
xream
ddc2564188 feat: 支持解析 QX VLESS 输入; VLESS 无 network 时, 默认为 tcp 2024-01-17 19:30:23 +08:00
xream
74834feb6a feat: SurgeMac 支持 external 2024-01-17 09:15:33 +08:00
xream
f0eca8f031 feat: 支持从 gist 获取不在同步配置中的 gist 文件 2024-01-17 01:10:54 +08:00
xream
50fe25ac1c fix: 不上传没有设置来源的同步配置 2024-01-16 23:41:51 +08:00
xream
90923b2650 chore: README 2024-01-16 20:52:46 +08:00
xream
76bc7c9bb1 feat: 取消 github 用户名绑定关系(现在用户名错误只影响头像), 增加最近一次 gist 检查状态 2024-01-16 09:44:02 +08:00
xream
49e975ba5e feat: 订阅链接可使用标准参数格式 #noCache&noFlow 或 井号附加 #noCache#noFlow 2024-01-16 08:11:34 +08:00
xream
afb2b19c66 fix: 修复 sing-box wireguard 输出 2024-01-16 07:24:30 +08:00
xream
b879d16442 feat: 以 #noFlow 结尾的远程链接不查询订阅流量信息 2024-01-16 07:07:55 +08:00
xream
f9eae3ec10 feat: VLESS URI 输入兼容 Shadowrocket 导出格式 2024-01-16 01:00:22 +08:00
xream
a2272de03f fix: 修复响应头缓存 2024-01-14 23:44:15 +08:00
xream
1436d4bd4e chore: Loon 插件支持修改响应头缓存有效期 2024-01-14 23:39:27 +08:00
xream
038efd6da6 feat: 优化订阅流量获取, 启用共享缓存(默认一分钟) 并优先尝试 HEAD 方法 2024-01-14 23:37:55 +08:00
xream
c5dd77fab2 feat: Node.js 版本体支持定时任务, 环境变量 SUB_STORE_BACKEND_CRON 2024-01-14 18:45:31 +08:00
xream
44556bc051 chore: 文案调整 2024-01-14 15:46:56 +08:00
xream
db67d0b809 fix: 找不到资源时报错; 调整脚本操作类型判断; 执行脚本失败时, 同时输出普通脚本和快捷脚本错误 2024-01-14 15:45:08 +08:00
xream
f586ba09fd revert: 回滚文件模板功能 2024-01-14 12:33:36 +08:00
xream
474d5fea57 feat: 脚本内部 produceArtifact 支持指定 produceType: 'internal', produceOpts: { 'include-unsupported-proxy': true } 来获得内部的数据结构; 订阅链接参数支持 type=internal&includeUnsupportedProxy=true; 文件支持 nunjucks 模板, 为 sing-box 增加的 Filter 用法 sub/col 为订阅/组合订阅中的节点名 {{ '订阅的name' | sub('美国|🇺🇸|us', 'i') }}, subNode/colNode 为订阅/组合订阅中的节点 {{ '订阅的name' | subNode('美国|🇺🇸|us', 'i') }}, 底层 produceArtifact('subscription', 'sing-box', 'internal', '美国|🇺🇸|us', 'i') 2024-01-14 12:13:29 +08:00
xream
a87eec14bd fix: 修复无脚本操作时 文件数据结构错误 2024-01-13 22:01:02 +08:00
xream
42ca08a970 revert: 同步接口支持 POST 2024-01-13 20:40:45 +08:00
xream
cec5f34eb9 fix: 同步接口支持 POST 2024-01-13 19:56:30 +08:00
xream
bfc00029ab fix: Surge Snell TFO 2024-01-13 16:51:48 +08:00
xream
f6c18367e9 feat: 脚本筛选的快捷操作支持 await 2024-01-13 13:55:07 +08:00
xream
ab6fc348b9 feat: 快捷脚本支持 await; 脚本操作支持 produceArtifact 2024-01-13 13:40:34 +08:00
xream
61df4d2144 feat: 支持输出到 sing-box; 文件脚本支持 ` ; 脚本支持 ProxyUtils.yaml` 2024-01-13 10:28:07 +08:00
xream
628e280383 feat: 文件支持远程/合并, /api/file/name 接口支持参数覆盖 2024-01-12 07:22:25 +08:00
xream
98a028e72a feat: 文件支持脚本操作 2024-01-12 06:16:39 +08:00
xream
30ee7bb2a9 feat: 同步配置支持文件 2024-01-12 03:52:41 +08:00
xream
ed76f4df8f feat: 文件接口 2024-01-12 02:23:57 +08:00
xream
9344dc64b0 fix: 兼容部分不带节点名的 URI 2024-01-08 09:44:53 +08:00
xream
9c220490fb fix: 兼容部分不带参数的 URI 输入 2024-01-08 09:28:33 +08:00
xream
d83fec84b7 feat: 支持全局请求超时(前端 > 2.14.29) 2024-01-08 07:22:03 +08:00
xream
f192af0a5c chore: 同步远程配置输出更多日志 2024-01-07 17:44:03 +08:00
xream
a14c87095f Node.js 版的通知支持第三方推送服务. 环境变量名 SUB_STORE_PUSH_SERVICE. 支持 Bark/PushPlus 等服务. 形如: https://api.day.app/XXXXXXXXX/[推送标题]/[推送内容]?group=SubStore&autoCopy=1&isArchive=1&sound=shake&level=timeSensitivehttp://www.pushplus.plus/send?token=XXXXXXXXX&title=[推送标题]&content=[推送内容]&channel=wechat 的 URL, [推送标题][推送内容] 会被自动替换 2024-01-02 22:52:33 +08:00
xream
b149a74785 feat: Wireguard 结构跟进 Clash.Meta, allowed_ips 改为 allowed-ips 2024-01-02 16:38:48 +08:00
xream
6f70cb323d feat: Trojan URI 支持 gRPC 2023-12-29 16:08:02 +08:00
xream
4b3fbb1400 release: backend version 2.14.135 2023-12-29 15:42:31 +08:00
xream
5f72443f58 Merge pull request #263 from Ariesly/ipv6-uri
fix: Handles node-info IPv6 address URIs
2023-12-29 15:39:03 +08:00
Ariesly
c02cc4bc62 fix: Handles node-info IPv6 address URIs 2023-12-29 07:10:47 +00:00
xream
d8ac3fc0dd doc: 补充文档 2023-12-27 02:55:04 +08:00
xream
00d0bd54fb feat: 支持 Surfboard(前端 > 2.14.27) 2023-12-27 00:28:15 +08:00
xream
3717630c49 feat: 当节点端口号为合法端口号时, 将类型转为整数(便于脚本判断) 2023-12-23 21:02:39 +08:00
xream
10e1bfd1e4 feat: 支持忽略失败的远程订阅(前端版本 > 2.14.20) 2023-12-18 01:42:33 +08:00
xream
b049f12e5b feat: 支持忽略失败的远程订阅(前端版本 > 2.14.20) 2023-12-18 01:41:37 +08:00
xream
e21a25e8f4 feat: 支持忽略失败的远程订阅(前端版本 > 2.14.20) 2023-12-18 01:24:48 +08:00
xream
5889bfa6cc feat: 兼容部分不规范的机场 Hysteria/Hysteria2 端口跳跃字段为空时 删除此字段 2023-12-17 18:31:12 +08:00
xream
337a0218fb feat: Hysteria2 URI 输入支持 hy2:// 2023-12-17 16:13:34 +08:00
xream
2445a683a4 fix: 过滤 Stash/Clash Shadowsocks cipher 2023-12-13 20:11:36 +08:00
xream
e5d8bfa29f feat: 环境变量 SUB_STORE_DATA_URL 启动时自动从此地址拉取并恢复数据 2023-12-13 09:54:57 +08:00
xream
c5c8ba9bbc feat: Node.js 前端代理后端路由 需设置环境变量 注意安全 SUB_STORE_FRONTEND_PATH=/prefix 2023-12-13 02:04:24 +08:00
xream
7b9ff7dfe0 feat: Node.js 前端代理后端路由 需设置环境变量 注意安全 SUB_STORE_FRONTEND_BACKEND_PATH=/prefix 2023-12-13 01:26:16 +08:00
xream
243e0ee457 feat: Node.js 前端代理后端路由 需设置环境变量 注意安全 SUB_STORE_FRONTEND_BACKEND_PATH=/prefix 2023-12-13 00:34:08 +08:00
xream
6c12216424 feat: Node.js 前端代理后端路由 需设置环境变量 注意安全 SUB_STORE_FRONTEND_PATH=/prefix 2023-12-13 00:26:11 +08:00
xream
a0cf875b4f feat: Node.js 前端代理后端路由 需设置环境变量 注意安全 SUB_STORE_FRONTEND_PATH=/prefix 2023-12-13 00:26:03 +08:00
xream
da05d68cef feat: Node.js 前端代理后端路由 2023-12-12 22:52:50 +08:00
xream
6c9c9ba056 fix: 修复 Loon Trojan WS 传输层 2023-12-12 21:13:17 +08:00
xream
dcdeac0b13 feat: 脚本筛选支持节点快捷脚本. 语法与 Shadowrocket 脚本类似
```
const port = Number($server.port)

return [80, 443].includes(port)
```
2023-12-11 11:57:12 +08:00
xream
a7e96cd696 feat: Node 版后端支持挂载前端文件夹, 环境变量 SUB_STORE_FRONTEND_PATH, SUB_STORE_FRONTEND_HOST, SUB_STORE_FRONTEND_PORT 2023-12-10 13:13:39 +08:00
xream
9574ddc090 chore: workflow 2023-12-10 09:32:56 +08:00
xream
43ea98794e fix: 处理 Hysteria2 URI 中的密码部分 2023-12-09 02:08:59 +08:00
xream
76c67c14b8 fix: 处理预览时子订阅出错的情况 2023-12-08 18:16:50 +08:00
xream
662268c546 fix: 处理 IPv6 地址 URI 2023-12-08 17:53:07 +08:00
xream
6d53326919 release: backend version 2.14.114 2023-12-08 11:49:11 +08:00
xream
66ff92fd79 Merge pull request #261 from Ariesly/master 2023-12-08 11:45:58 +08:00
Ariesly
610f71a33b fix(hysteria2): For shadowrocket obfs 2023-12-08 01:51:54 +00:00
Ariesly
9aee487fd6 chore(hysteria2): Uri support with tfo 2023-12-08 01:34:53 +00:00
Ariesly
4b4dbb5377 fix(hysteria2): Change to TLS Fingerprint 2023-12-08 01:30:09 +00:00
xream
dd52573ada release: backend version 2.14.113 2023-12-07 18:15:21 +08:00
xream
79108d18ac Merge pull request #260 from Ariesly/master
feat: Hysteria2 URI
2023-12-07 18:03:26 +08:00
Ariesly
decce03905 feat: Hysteria2 URI 2023-12-07 06:25:33 +00:00
xream
92cb6446ad feat: ProxyUtils.produce 增加第二个参数 type, 暂时仅支持目标为 ClashMetainternal 输出节点数组供开发者使用 2023-12-05 21:53:22 +08:00
xream
beef9ac1bb fix: 默认 User-Agent 改为 clash.meta 后, 调整订阅预处理器的逻辑, 减少 Base64 误判 2023-12-05 12:43:13 +08:00
xream
142ddbabb5 feat: 手动下载备份文件和使用备份上传恢复(前端版本 > 2.14.15) 2023-12-04 16:07:10 +08:00
xream
50af686f22 fix: 修复 Base64 内容的判断 2023-12-02 16:14:11 +08:00
xream
92d78b605a fix: 修复多行订阅流量(仅传递首个订阅的流量信息) 2023-12-01 17:09:56 +08:00
xream
2015f60b1f feat: 支持不规范的 Loon ss+simple obfs 协议格式 2023-11-30 16:01:13 +08:00
xream
22c7ab8d5d feat: 支持按顺序合并本地和远程订阅(前端版本 > 2.14.14 可输入) 2023-11-29 03:57:20 +08:00
xream
46498234b7 feat: 远程订阅支持换行符连接的多个订阅链接(前端版本 > 2.14.13 可输入) 2023-11-29 02:24:03 +08:00
xream
3a349e5f09 fix: SS URI 端口取整数部分 2023-11-28 23:14:45 +08:00
xream
663bcc6d9e feat: 支持设置默认 User-Agent 2023-11-25 04:31:17 +08:00
xream
e5d8d49003 feat: 后端支持自定义 hostport. 环境变量 SUB_STORE_BACKEND_API_HOST 默认 ::, SUB_STORE_BACKEND_API_PORT 默认 3000 2023-11-24 18:31:13 +08:00
xream
73b4f651d0 feat: 增加了节点字段 1. no-resolve, 可用于跳过域名解析 2. resolved 用来标记域名解析是否成功 2023-11-21 20:10:05 +08:00
xream
a664da6541 fix: Hysteria/Hysteria2 输出到 Stash 时 down-speed 和 up-speed 字段截取数字部分 2023-11-20 11:22:01 +08:00
xream
64dcb8dc53 Merge pull request #250 from YES-Lee/patch-1
feat: add sync task for qx
2023-11-19 11:44:04 +08:00
xream
a034f5c98c fix: 修复 app 版参数 2023-11-16 12:49:06 +08:00
xream
aa76e53a70 feat: 补全 Surge 全协议的 no-error-alert 和 ip-version 字段 2023-11-15 15:16:34 +08:00
xream
3314ad5f0e feat: Sub-Store 生成的订阅地址支持传入 订阅链接/User-Agent/节点内容 可以复用此订阅的其他设置
例如: 建一个 name 为 sub 的订阅, 配置好节点操作

以后可以自由传入参数 无需在 Sub-Store 前端创建新的配置

`/download/sub?target=Surge&content=encodeURIComponent编码过的本地节点`

`/download/sub?target=Surge&url=encodeURIComponent编码过的订阅链接&ua=encodeURIComponent编码过的User-Agent`
2023-11-14 21:46:56 +08:00
xream
cca259b63c feat: 脚本链接的末尾加上 #noCache 关闭缓存 2023-11-14 21:14:47 +08:00
xream
27e8967df8 feat: 脚本操作支持节点快捷脚本. 语法与 Shadowrocket 脚本类似
```
$server.name = '前缀-' + $server.name
$server.ecn = true
$server['test-url'] = 'http://1.0.0.1/generate_204'
```
2023-11-14 17:07:01 +08:00
xream
410b02d4c7 feat: 脚本操作 ProxyUtils 增加了 isIPv4, isIPv6, isIP 方法 2023-11-14 00:57:52 +08:00
xream
4695a65d7f feat: 兼容某些格式的 Trojan URI(首个 # 之后的字符串均视为节点名称) 2023-11-13 18:49:50 +08:00
xream
b1d8d502f8 feat: 支持 QX tls-pubkey-sha256 tls-alpn tls-no-session-ticket tls-no-session-reuse 字段 2023-11-13 14:34:36 +08:00
xream
a49aaa4fab feat: 支持 QX server_check_url 和 Stash benchmark-url 字段 2023-11-13 14:06:44 +08:00
xream
4af31dd922 feat: 兼容传输层 headers 中小写的 host 字段 2023-11-08 09:54:53 +08:00
xream
09d4902f41 feat: 兼容某些机场订阅 hysteria 节点中的 auth_str 字段(将会在未来某个时候删除 但是有的机场不规范) 2023-11-08 07:44:12 +08:00
xream
956fa20af5 feat: 为脚本操作增加流量信息操作 flowUtils 2023-11-07 16:42:28 +08:00
xream
fd68656a76 feat: 兼容另一种 username password 格式 2023-10-31 21:59:34 +08:00
xream
50ed7d7241 doc: pnpm 2023-10-30 01:44:18 +08:00
xream
b176a313e2 feat: 支持 Loon Hysteria2(ecn, 流量控制参数未知) 2023-10-29 23:04:56 +08:00
xream
18903710ac fix: 简单限制一下订阅/组合订阅的名称(不可包含 "/" ) 2023-10-29 22:38:20 +08:00
xream
63c6b627cc feat: 订阅链接支持参数(例: https://foo.com#noCache 关闭缓存) 2023-10-26 11:26:31 +08:00
Johnson
f796f445b1 feat: add qx sync task 2023-10-25 10:44:22 -05:00
xream
af7724612b feat: Surge 支持 block-quic 参数 2023-10-24 09:31:48 +08:00
xream
723d5e6e1b feat: Surge 全协议支持 Shadow TLS, 部分协议增加 TLS Fingerprint 支持 2023-10-24 07:26:34 +08:00
xream
33b345ad22 feat: 支持 Shadowrocket Hysteria2 URI 格式输入 2023-10-18 23:48:45 +08:00
xream
feb1b45102 fix: Surge Hysteria2 输出重复添加 tfo 的 bug 2023-10-18 05:09:10 +08:00
xream
f7bfaea4f0 feat: Base64 订阅关键词增加 VLESS 和 Hysteria2 2023-10-16 22:11:26 +08:00
xream
105b91a699 feat: Stash Hysteria2 2023-10-15 15:55:19 +08:00
xream
b6e3d81807 feat: Surge Hysteria2 输入/输出增加 ecn 参数 2023-10-12 22:15:10 +08:00
xream
7a76874a6d feat: Surge TUIC 输入/输出增加 ecn 参数 2023-10-12 22:09:58 +08:00
xream
228c8862e1 feat: Surge Hysteria2 输出增加 download-bandwidth(若有值但解析失败则为 0) 2023-10-12 00:39:10 +08:00
xream
0509530caa feat: 输入增加 Hysteria2 URI 支持; Surge Hysteria2 输出增加 fingerprint 2023-10-11 23:35:42 +08:00
xream
8515b8ad15 feat: Surge 输入输出增加 Hysteria2 2023-10-09 23:42:22 +08:00
xream
ad3b12fa3a feat: 兼容更多 VMess URI 格式 2023-10-09 17:36:11 +08:00
xream
9dec5d9ccf feat: Surge Snell 输入支持解析 reuse 字段 2023-10-08 16:42:35 +08:00
xream
cc8ba7782e feat: proxy 增加 subName(订阅名), collectionName(组合订阅名); 脚本增加第三个参数 env(包含订阅/组合订阅/环境/版本等信息) 2023-10-08 13:21:22 +08:00
xream
cdbd31f265 [+] 重复节点通知中增加订阅名称和重复节点名称 2023-10-02 16:21:08 +08:00
xream
3c9da46f13 [+] Surge 输出支持 underlying-proxy; VMess/Vless URI 支持 gRPC mode(默认为 gun) 2023-10-01 22:05:51 +08:00
xream
cfd12e0da7 feat: 支持 hysteria2 2023-09-22 14:43:43 +08:00
xream
a778d2e222 fix: 修复部分环境无 clearTimeout 的问题 2023-09-18 20:09:03 +08:00
K
ed1fa5d675 fix @ 2023-09-15 18:52:21 +08:00
xream
32a664676f fix: 修复 sub-store-0 路由 2023-09-15 18:42:53 +08:00
xream
cbba784d84 fix: 修复文件和模块命名/重复添加的逻辑 2023-09-15 10:08:36 +08:00
xream
b0c4b03175 feat: 文件和模块接口获取原始内容; 文件列表不返回原始内容 2023-09-14 18:51:23 +08:00
xream
fda828ceae feat: 模块接口; 脚本参数支持 JSON 和 URL编码 2023-09-14 17:34:24 +08:00
xream
9efc2087a1 feat: 实验性支持本地脚本复用 2023-09-14 08:56:33 +08:00
xream
71f4695c3b feat: 支持 Clash VLESS 输出(与 Clash.Meta 的区别为: 无 XTLS 2023-09-11 02:35:36 +08:00
xream
d5a4b24209 fix: 修复了 Clash.Meta 输出 VLESS 时 内部字段 sni 未作用到 servername 的问题 2023-09-09 14:03:40 +08:00
xream
3cbeba07b6 fix: 预览时脚本下载报错导致的崩溃 2023-09-07 23:17:36 +08:00
Peng-YM
6b1fa38b4b release: backend version 2.14.49 2023-09-04 23:16:52 +08:00
Peng-YM
a6e54c7560 feat: added the ability to change the base path for the data files
before starting node, use the command `export SUB_STORE_DATA_BASE_PATH="<YOUR_PATH>"`
2023-09-04 23:16:13 +08:00
xream
9937b07557 fix: 排序接口修正为使用 name 排序 2023-09-04 21:31:55 +08:00
xream
b2878d8a2a feat: 为 Gist 备份还原增加基础校验逻辑 2023-09-04 17:06:37 +08:00
xream
362cbe9686 feat: 支持 Surge WireGuard 的输入和输出(由于 Surge 配置的特殊性, 仅支持 同进同出) 支持的字段格式: HK WARP = wireguard, section-name=Cloudflare, no-error-alert=true, underlying-proxy=HK, test-url=http://1.0.0.1/generate_204, ip-version=v4-only 2023-09-01 02:44:43 +08:00
xream
e8e903f630 fix: 暂时将后端上传限制放宽到 1mb 2023-09-01 02:07:24 +08:00
Peng-YM
b57b520aa0 Update README.md 2023-08-31 16:41:50 +08:00
xream
a77d5dd5c9 docs: config 2023-08-30 22:52:05 +08:00
xream
954f08fca0 chore: sync to GitLab 2023-08-30 16:19:17 +08:00
xream
6ac729c7e6 chore: 调整部分日志 2023-08-29 13:52:02 +08:00
xream
d97766f1ba docs: 更新 Surge SSR 协议说明; 模块说明页增加更新说明的链接 2023-08-29 01:59:01 +08:00
xream
f9b1f82e31 feat: 根据 UA 识别 macOS 版 Surge(也可指定参数 target=SurgeMac) 并支持 SSR 协议(节点字段 exec 为 ssr-local 路径, 默认 /usr/local/bin/ssr-local; 端口从 10000 开始递增, 暂不支持配置) 2023-08-29 01:46:49 +08:00
xream
41816cb0d8 feat: 统一将 VMess 和 VLESS 的 http 传输层的 path 和 Host 处理为数组 2023-08-28 23:47:10 +08:00
xream
4ca9ab33b1 fix: 仅在 VMess/VLESS 且传输层为 http 时设置 Host 为数组 2023-08-28 23:38:03 +08:00
xream
272974ff39 feat: 增加还原备份完成的日志输出 2023-08-28 23:29:53 +08:00
xream
bdb8a963a1 chore: changelog 2023-08-28 23:15:48 +08:00
xream
a0ab168a42 fix: build dist 2023-08-28 20:41:40 +08:00
xream
9c59a8fe1b fix: 非 tls, 有 ws/http 传输层, 使用域名的节点, 将设置传输层 Host 防止之后域名解析后丢失域名(不覆盖现有的 Host) 2023-08-28 20:34:22 +08:00
Peng-YM
ba49a50580 chore: remove unnecessary files 2023-08-28 20:07:32 +08:00
Peng-YM
d1fcd2c048 chore: removed tracking dist files from git 2023-08-28 20:06:50 +08:00
Peng-YM
92880fece6 remove: deprecated artifact 2023-08-28 20:01:05 +08:00
xream
de2d78e371 feat: bundle 2023-08-28 19:01:34 +08:00
xream
3bae9dc896 fix: sync 2023-08-28 18:48:33 +08:00
xream
e573aa0da7 fix: vless servername 2023-08-28 15:32:08 +08:00
xream
b8b47c74dc feat: new target platform "Clash.Meta" 2023-08-28 13:10:48 +08:00
xream
cc593bddda feat: 非 tls, 有 ws/http 传输层, 使用域名的节点, 将设置传输层 Host 防止之后域名解析后丢失域名 2023-08-28 00:09:24 +08:00
xream
3958333aee feat: 域名解析新增 Tencent, Ali; 脚本下载失败, 脚本操作失败, 脚本过滤失败时都会报错了 2023-08-27 23:17:57 +08:00
xream
d5993db6cb fix: transport headers may have no Host 2023-08-27 18:17:30 +08:00
xream
b46c83453f feat: VLESS URI 2023-08-27 00:57:21 +08:00
xream
994863739e feat: 组合订阅错误信息将包含出现错误的子订阅名称; 获取流量失败时, 不影响节点订阅; 订阅上游无有效节点时将报错 2023-08-26 20:27:12 +08:00
xream
0323551382 feat: Loon WireGuard 2023-08-26 15:00:46 +08:00
xream
ac239e8ba2 feat: wireguard 2023-08-25 22:48:03 +08:00
xream
3a3988ee52 feat: hysteria 2023-08-25 16:19:08 +08:00
Hsiaoyi
0d7ae57daa Merge pull request #233 from eltociear/master-1
Fix typo in README.md
2023-08-24 21:46:03 +08:00
xream
02ea0f360c feat: producers adjustments, VMess URI formats 2023-08-24 21:43:58 +08:00
xream
fdb89a3a67 feat: Added support for VMess URI in other formats and VMess without transport settings 2023-08-24 20:23:48 +08:00
xream
54515fcc58 fix: 兼容 value 为空的 Trojan URI 2023-08-24 11:38:27 +08:00
xream
ad559cafac fix: trojan network tcp 2023-08-24 11:08:43 +08:00
xream
6b56a34d1a fix: trojan uri and tls 2023-08-24 10:02:03 +08:00
Ikko Eltociear Ashimine
de621d90a1 Fix typo in README.md
Speicial -> Special
2023-08-24 00:46:24 +09:00
xream
95b3b046f3 fix: servername/sni priority over wss host 2023-08-22 18:21:34 +08:00
xream
b2384a8736 fix: servername/sni priority over wss host 2023-08-22 17:28:39 +08:00
xream
245afe43f2 fix: VMess URI alterId parseInt 2023-08-22 15:29:55 +08:00
xream
56c2cfb903 fix: QX tls 2023-08-22 00:42:53 +08:00
xream
a77f738676 fix: QX tls 2023-08-22 00:08:53 +08:00
xream
def5c3d533 fix: vmess/vless http-opts.path/http-opts.headers.Host must be an array in some clients 2023-08-21 22:16:07 +08:00
xream
dc16b980d6 fix: double quotes in Surge vmess ws-headers Host 2023-08-21 21:20:31 +08:00
xream
eb386a180f Surge 默认模块不带 ability 参数; 分离出固定带参和不带参的模块 2023-08-20 17:22:51 +08:00
walkxspace
a09d91e8cd Update geo.js (#231) 2023-08-19 11:44:55 +08:00
xream
4a9b72b058 feat: Added replaceArtifact API 2023-08-18 13:48:37 +08:00
xream
b0114f2b29 fix: flexible cipher for Loon 2023-08-15 21:22:33 +08:00
xream
4bb8ad9626 feat: Added support for scy of VMESS URI 2023-08-15 18:15:04 +08:00
xream
1502c40b3d feat: Added support for SNI & allowInsecure of Trojan URI 2023-08-15 17:25:25 +08:00
xream
0d84a7bd6b feat: Added replaceSubscriptions, replaceCollection API 2023-08-15 15:48:57 +08:00
xream
2e80c13fa5 Fixed: fast-open tfo 2023-08-15 14:59:27 +08:00
xream
6ec276472d feat: Added support for SS URI in other formats 2023-08-15 01:48:54 +08:00
xream
79e748d98b Fixed: unsupported cipher for Clash/Stash 2023-08-14 10:04:47 +08:00
Hsiaoyi
2fe79008bc Merge pull request #229 from xream/feature/tuic
Adjust the logic for determining the tuic version
2023-08-13 17:03:40 +08:00
xream
cad433b84c Adjust the logic for determining the tuic version 2023-08-13 17:00:44 +08:00
Hsiaoyi
66f7ef047e Merge pull request #228 from xream/feature/tuic
feat: Added support for tuic and some compatibility adjustments
2023-08-13 15:56:52 +08:00
xream
6445374b2e feat: Added support for tuic and some compatibility adjustments 2023-08-13 15:54:04 +08:00
Hsiaoyi
17e47d40f0 Merge pull request #227 from xream/feature/snell
feat: Added support for producing snell nodes with reuse and optional obfs
2023-07-31 18:44:53 +08:00
xream
823fe52933 feat: Added support for producing snell nodes with reuse and optional obfs 2023-07-31 18:41:48 +08:00
Hsiaoyi
f76a1aa267 Merge pull request #226 from Keywos/master
fixed deleted gist
2023-07-31 17:42:49 +08:00
Hsiaoyi
a2b57f7885 Fixed: empty uploading files 2023-07-31 17:42:31 +08:00
K
5ca0dfdcca version 2.14.3 2023-07-31 16:41:22 +08:00
K
140450d3c7 fixed deleted gist 2023-07-31 16:38:07 +08:00
K
ad94c25914 Merge branch 'sub-store-org:master' into master 2023-07-31 14:58:54 +08:00
Hsiaoyi
cfc6a944fa Added Surge-Noability.sgmodule 2023-07-28 22:38:36 +08:00
Hsiaoyi
1449313265 Merge pull request #223 from xream/feature/V2Ray
feat: V2Ray Producer
Fixes #180
2023-07-26 09:55:45 +08:00
xream
0d58b4c845 feat: V2Ray Producer 2023-07-26 09:48:14 +08:00
Hsiaoyi
c82c8db56b Update Surge.sgmodule 2023-07-26 09:03:59 +08:00
K
501e153dbf Update Surge.sgmodule 2023-07-26 01:48:47 +08:00
Hsiaoyi
e2d0428d99 Update Surge.sgmodule
ability=http-client-policy
2023-07-24 14:39:21 +08:00
Hsiaoyi
4c446e8335 Merge pull request #219 from Keywos/script-Cache
surge
2023-07-23 18:00:42 +08:00
Hsiaoyi
ed359c28e3 Merge pull request #220 from xream/fix/FullConfig
fix: Full Config Preprocessor
2023-07-23 17:42:06 +08:00
xream
c3a1d9cec6 fix: Full Config Preprocessor 2023-07-23 17:38:29 +08:00
K
a1b715db0c [!] Surge 2023-07-23 15:11:32 +08:00
K
a07c055152 Merge branch 'script-Cache' of https://github.com/Keywos/Sub-Store into script-Cache 2023-07-22 15:24:20 +08:00
K
1843d3d083 [-] log 2023-07-22 15:24:19 +08:00
Hsiaoyi
c0d8f51523 Merge pull request #218 from sub-store-org/dependabot/npm_and_yarn/backend/axios-0.21.2
build(deps-dev): bump axios from 0.20.0 to 0.21.2 in /backend
2023-07-22 14:35:52 +08:00
Hsiaoyi
dd88e6c693 using Node.js v16 2023-07-22 14:34:06 +08:00
dependabot[bot]
68da7052fa build(deps-dev): bump axios from 0.20.0 to 0.21.2 in /backend
Bumps [axios](https://github.com/axios/axios) from 0.20.0 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.2/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.20.0...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-22 06:17:45 +00:00
Hsiaoyi
cafce794e9 Merge pull request #217 from Keywos/script-Cache
[+] version 2.14.0
2023-07-22 14:15:56 +08:00
K
cb745297e5 Merge branch 'sub-store-org:master' into script-Cache 2023-07-22 14:13:09 +08:00
K
3a27c9ac9c [+] version 2023-07-22 14:11:45 +08:00
Hsiaoyi
6af8c916b7 Merge pull request #216 from Keywos/script-Cache
script-Cache
2023-07-22 13:56:54 +08:00
K
078e813a29 script-Cache 2023-07-22 13:53:47 +08:00
Hsiaoyi
bef9652732 fix: tasks cache in Node.js environment (#209) 2023-05-09 17:16:35 +08:00
Hsiaoyi
b207fb284f feat: Added support for producing Surge nodes with test-url (#199) 2023-03-19 18:32:34 +08:00
NobyDa
315cf22c54 Improve resource cache key. (#190) 2023-02-08 12:30:26 +08:00
QuentinHsu
8d5b2c7348 🐞 fix(subscriptions): negative usage flow (#175) 2022-10-25 00:07:23 +08:00
Jasonzza
ec4e415df4 fix: sync artifacts issue (#164) 2022-09-11 23:52:51 +08:00
Peng-YM
4d69dc2e78 fix: cron-sync-artifacts.js path 2022-09-10 11:47:34 +08:00
Peng-YM
2251565e24 build: Update config files 2022-09-10 00:48:11 +08:00
Peng-YM
e56c9c84dd build: Bump version 2022-09-09 23:25:25 +08:00
Peng-YM
e2c35d4e46 build: Update GitHub action 2022-09-09 23:15:28 +08:00
Peng-YM
60c1c3d2a5 build: Split sub-store.min.js for better performance on iOS devices 2022-09-09 23:11:13 +08:00
Peng-YM
6e2d9024a6 build: Build minimized JavaScripts files 2022-09-09 21:07:28 +08:00
Peng-YM
baa130b0f7 fix (ip-flag.js): Use batch processing to reduce memory footprint 2022-09-09 21:05:05 +08:00
Peng-YM
7b2b246b92 fix (core): Fix QX vmess-http producer
closes #162
2022-09-09 21:04:08 +08:00
Peng-YM
b3c80147dc fix (core): Proxy port smaller that 80 is now allowed
close #167
2022-09-09 21:03:01 +08:00
Peng-YM
ac60d34866 refactor (core): Reworked Trojan URI parser to support IPV6 nodes 2022-08-13 12:57:49 +08:00
Peng-YM
e9b9c78101 fix: xchacha20-ietf-poly1305 cipher is now supported
closes #151
2022-08-13 11:22:23 +08:00
Peng-YM
164a9cb635 fix: ip-flag does not work on Loon
The node parameter should not include proxy name
2022-08-12 00:00:11 +08:00
Peng-YM
70fbb79d7b feat: Add ip-flag script 2022-08-11 22:58:48 +08:00
Peng-YM
78479529ae perf: Add support for sending http requests using specific nodes
Only supported on Loon & Surge
2022-08-11 01:07:16 +08:00
Peng-YM
694cf93f85 fix: Occasional crashed when performing migration 2022-08-10 00:28:46 +08:00
Peng-YM
5059269227 feat: Include cron-sync-artifact in Stash configuration 2022-08-09 22:42:13 +08:00
Peng-YM
8b8bfe123e fix: Rename subscription and collection will break artifacts 2022-08-09 22:28:45 +08:00
Peng-YM
1f05f42649 fix: proxy duplicate issue 2022-08-04 20:40:40 +08:00
Peng-YM
903d7d8bbe fix: Loon shadowsocksr obfs-param incorrect 2022-08-02 09:23:34 +08:00
Peng-YM
3541f24da7 perf (core): DomainResolveProcessor now cache results 2022-07-19 21:29:06 +08:00
Peng-YM
93a98e4cec fix (cron-sync-artifact): sync timeout due to missing await 2022-07-19 20:55:18 +08:00
Peng-YM
f27ce7ee48 fix (config): Sub-Store url is incorrect in Surge.sgmodule 2022-07-13 15:14:11 +08:00
Peng-YM
8a95e17637 chore (gh-action): Trigger workflow run only if package.json have been modified 2022-07-13 15:10:30 +08:00
Peng-YM
97b7b0c774 perf: Use the latest release scripts in configs 2022-07-13 14:53:44 +08:00
Peng-YM
bd63842c50 chore: Update GitHub action to automatically release new version 2022-07-13 14:41:34 +08:00
Peng-YM
392def5ac1 fix (product): cron-sync-artifacts not working 2022-07-13 14:03:43 +08:00
github-actions@github.com
d7b5d42148 Release 2022-07-13 02:32:52 +00:00
Peng-YM
f62a79fedb fix (core): trojan sni is lost when parsing Clash nodes
#build
2022-07-13 10:31:08 +08:00
Peng-YM
1a85895c01 fix (restful): Add query field in IP-API 2022-07-12 23:15:29 +08:00
Peng-YM
d234a8c314 perf: Add switch for cron-sync-artifacts 2022-07-12 18:56:24 +08:00
Peng-YM
5d57ce2859 fix (restful): Intercept IP-API query failed message when querying node info 2022-07-12 18:34:31 +08:00
Peng-YM
bdb8096339 perf: Include cron script for syncing artifacts in configurations 2022-07-12 18:15:05 +08:00
Peng-YM
b767c8146b feat (restful): Add /api/utils/node-info for querying proxy node info 2022-07-12 15:09:44 +08:00
Peng-YM
9b2adfacc6 chore: Use pnpm in GitHub action 2022-07-11 23:38:57 +08:00
Peng-YM
e41e4968cd fix: Vmess auto/none cipher parsed incorrectly 2022-07-11 23:33:06 +08:00
Peng-YM
44622b43dc fix (core): Clash Vmess servername does parse correctly 2022-07-11 23:20:21 +08:00
Peng-YM
6ca5a525a0 feat (restful): Add /api/utils/refresh
The API call does the following:
- Fetch GitHub avatar and update artifact store url
- Revoke all cached resources
2022-07-11 23:06:49 +08:00
Peng-YM
5698208a47 perf: Use cache for all remote resources 2022-07-11 20:46:16 +08:00
Peng-YM
51e3593ac2 feat: Add support for targetPlatform ShadowRocket 2022-07-11 18:23:56 +08:00
Peng-YM
0f7e995fc0 perf: Modify revert.js to completely clear sub-store cache 2022-07-11 18:22:42 +08:00
Peng-YM
15511b0552 fix: Backend crash when operator argument is undefined 2022-07-08 16:28:47 +08:00
Peng-YM
03a64823e4 fix: Error codes are not shown in the response 2022-07-08 11:00:26 +08:00
Peng-YM
51353fbd5d fix: Restrict artifact name in order to fix a Gist API bug 2022-07-08 10:47:38 +08:00
Peng-YM
134375c31b fix: Sync time is updated even if upload failed 2022-07-08 10:26:05 +08:00
Peng-YM
22fc800e45 fix: Full config preprocessor bug 2022-07-07 23:24:01 +08:00
Peng-YM
41478d28e8 feat: Add artifactStore URL in settings 2022-07-07 22:40:46 +08:00
Peng-YM
72f7606062 refactor: Revise sync artifacts APIs
- /api/sync/artifacts: sync all artifacts
- /api/sync/artifact/:name: sync a specific artifact
2022-07-07 14:47:09 +08:00
Peng-YM
ee13ebba0b refactor: Refactor the arguments of the Flag Operator 2022-07-06 18:12:36 +08:00
Peng-YM
449bfe6fca fix: Set default subscription source to remote when migrating data 2022-07-06 18:12:36 +08:00
Peng-YM
ca9ca76ad9 refactor: Add new frontend as submodule 2022-07-06 18:12:36 +08:00
Peng-YM
1e07a3c733 feat: Added Conditional Filter 2022-07-06 18:12:36 +08:00
Peng-YM
8679e6b240 fix: Useless filter bug 2022-07-06 18:12:36 +08:00
Peng-YM
8b3bbb5e04 fix: Perform migration after restoring data from gist 2022-07-06 18:12:36 +08:00
Peng-YM
2954b1af40 chore: nodemon now ignore the changes of sub-store.json 2022-07-06 18:12:36 +08:00
Peng-YM
993658ff0b refactor: Standardize error handling for RESTful APIs 2022-07-06 18:12:36 +08:00
Peng-YM
1f20c00c33 feat: Fetch avatar from GitHub automatically 2022-07-06 18:12:36 +08:00
Peng-YM
62e100ba55 fix: QuickSettingOperator does not respect default settings 2022-07-06 18:12:36 +08:00
Peng-YM
8e5a85a402 fix: Useless filters are not migrated 2022-07-06 18:12:36 +08:00
Peng-YM
6d5e8cd674 fix: Database updateByName not working 2022-07-06 18:12:36 +08:00
Peng-YM
e4ad4df1e0 fix: Process ids are now preserved in migration 2022-07-06 18:12:36 +08:00
Peng-YM
2bb27fbcb7 feat: Add sorting API
Added POST /api/sort/subs, /api/sort/collections, /api/sort/artifacts for sorting subs, collections, and artifacts.
2022-07-06 18:12:36 +08:00
Peng-YM
83d7d789a9 refactor: Migrate to API v2
- Added auto schema migration
- Refactored /api/subs, /api/collections, /api/artifacts. Now these APIs will return array instead of object. This enables sorting items in the future.
2022-07-06 18:12:36 +08:00
Peng-YM
b50e0b3523 chore: Migrate from npm to pnpm for better performance 2022-07-06 18:12:36 +08:00
Peng-YM
5a5b39a3ca doc: Change LICENSE to AGPLv3 2022-07-06 18:11:49 +08:00
github-actions@github.com
1304c3f35b Build sub-store.min.js 2022-07-04 03:47:46 +00:00
Virgil Clyne
55982972b2 fix (geo.js): adjust the China flag order (#138)
#build
2022-07-04 11:45:47 +08:00
Peng-YM
4ff1317074 fix: Failed to parse Surge/QX nodes with tls fingerprint 2022-07-02 22:53:09 +08:00
Peng-YM
b4d5003d0e feat (backend): Add preview API
- POST /api/preview/sub for previewing subs
- POST /api/preview/collection for previewing collection
2022-07-01 17:36:48 +08:00
Peng-YM
818e94f41d refactor: Download API
Now the download APIs are moved into a new file
2022-06-30 12:19:43 +08:00
Peng-YM
8647f9fd59 fix: Backend crashes when invalid url is used
Improve error handling for flow info API
2022-06-29 14:07:22 +08:00
Peng-YM
c929bb3e48 perf: Improve /utils/env API
- Add version number
- Add support for Stash & ShadowRocket backend
2022-06-29 11:25:57 +08:00
Peng-YM
002428d8ff feat (backend): Add backend API to get flow info for subscriptions 2022-06-29 00:12:31 +08:00
Peng-YM
f8671dc8a9 docs: Add version number into products 2022-06-28 13:10:52 +08:00
Peng-YM
0e52e3c67c fix(geo.js): ISO Code at Start/End of String (#137) 2022-06-28 12:52:52 +08:00
github-actions@github.com
12acadb5c4 fix(geo.js): separate ISO Code from Country Name (#133)
#build
Build sub-store.min.js
2022-06-28 12:44:24 +08:00
Jacob Lee
2419ae5374 feat: add preview specific platform feature (#131) 2022-06-25 13:17:12 +08:00
Jacob Lee
0e196dbed7 Fix: fix the issue that speed dial auto-adaptation theme does not work and is overwritten (#130) 2022-06-24 22:15:06 +08:00
Peng-YM
c84aa4eb8b fix (proxy-parser): ws-headers with multiple keys are not correctly handled when parsing Surge proxies
closes #120
2022-06-24 18:01:31 +08:00
Jacob Lee
33c974ca93 feat (UI/sub-editor): Add speed dial for quick saving and adding operations (#129) 2022-06-24 17:22:59 +08:00
QuentinHsu
107f04067f perf (UI): Use display name in collections
closes #127
2022-06-24 14:41:37 +08:00
Peng-YM
28fa2b8fb7 fix (proxy-producer): ws-headers keys are lost when producing Surge nodes
closes #119
2022-06-23 17:59:34 +08:00
Peng-YM
f885d171a5 perf: Allow using full configuration as subscription source
Supported configuration formats: Loon, QX, and Surge

closes #121
2022-06-23 15:31:54 +08:00
Hsiaoyi Hsu
c74834a9b1 feat: Added support for display name 2022-06-22 23:08:15 +08:00
github-actions@github.com
161f455da1 Build sub-store.min.js 2022-06-22 14:31:15 +00:00
Virgil Clyne
6657fe1b05 feat: Update getFlag() #build
Update geo.js
2022-06-22 22:29:56 +08:00
Peng-YM
0225e5f081 chore: Add dev branch to GitHub action 2022-06-22 22:25:15 +08:00
Peng-YM
3cfabe703d docs: replaced the SIP002 link in commet 2022-06-22 19:25:19 +08:00
Peng-YM
b3630ae9e2 Update README.md 2022-06-21 22:56:38 +08:00
github-actions@github.com
2359a221d7 Build sub-store.min.js 2022-06-21 09:04:24 +00:00
Peng-YM
e38f056a41 fix: unable to delete subscription with trailing spaces #build 2022-06-21 17:02:57 +08:00
Peng-YM
3999c61987 feat: Added mocha test to GitHub action 2022-06-21 17:01:31 +08:00
Peng-YM
2c84b3d6cd fix: vmess aead 2022-06-21 17:00:50 +08:00
Peng-YM
def42d683c feat: Added cron script for syncing all artifacts to gist 2022-06-21 16:01:02 +08:00
Peng-YM
5cb2e3c105 Added github workflow for building dist files 2022-06-21 15:48:55 +08:00
Peng-YM
092e873eb8 Fix QX SSR issue 2022-06-21 09:24:37 +08:00
Peng-YM
98af5051cb feat: Reworked Loon producer 2022-06-20 22:58:27 +08:00
Peng-YM
62501fd2dd feat: Added support for parsing Loon vless, Surge socks5 and snell proxies 2022-06-20 22:10:29 +08:00
Peng-YM
7f6d55c635 feat: Added dedicated Stash producer 2022-06-20 21:51:13 +08:00
Peng-YM
8082c0d28d Fixed min.js not found issue 2022-06-20 20:38:54 +08:00
Peng-YM
287082027e Fix trojan URI issues 2022-06-20 19:32:57 +08:00
Peng-YM
da44dc9cab Various bug fixes for URI format parsing 2022-06-20 17:01:09 +08:00
QuentinHsu
fc86f3e15d feat: add version info in console.log (#117) 2022-06-20 14:14:03 +08:00
Peng-YM
56abcf29f7 Fixed QX trojan wss issue 2022-06-20 11:11:53 +08:00
Peng-YM
e916c934e6 Now an error will be thrown if a required field of a proxy is missing 2022-06-20 01:33:24 +08:00
Peng-YM
3db239115a Fixed QX tls issue 2022-06-19 01:14:38 +08:00
Hsiaoyi Hsu
f2538a2636 Removed Surge vmess encrypt-method (#116) 2022-06-18 10:15:02 +08:00
Peng-YM
8451cfe0d6 Minor bug fix 2022-06-17 22:39:41 +08:00
Peng-YM
30e5e079d5 Set vmess alterId to 0 if not specified 2022-06-17 22:19:02 +08:00
Peng-YM
b46fc08d76 Reworked Surge producer 2022-06-17 22:12:01 +08:00
Peng-YM
4cb6b295f0 Minor bug fixes 2022-06-17 20:00:26 +08:00
Peng-YM
a95cb94ee7 Fixed QX vmess method auto 2022-06-16 18:47:40 +08:00
Peng-YM
40f6052231 Reworked QX producer 2022-06-16 18:35:24 +08:00
Peng-YM
a72a879a23 Minor bug fix for chacha20-ietf-poly1305 2022-06-16 16:23:54 +08:00
Peng-YM
e4034ad480 Refactored proxy producers structure 2022-06-16 14:37:51 +08:00
Peng-YM
e7f0259eaf Use babel relative import path 2022-06-16 14:24:32 +08:00
Peng-YM
469baf8281 Refactored proxy-utils structure 2022-06-16 12:58:24 +08:00
Peng-YM
4ab1e3cdaa Reintroduce storage management API, closes #113 2022-06-16 11:28:15 +08:00
Peng-YM
dcc017182f Minor refactor 2022-06-16 11:19:07 +08:00
Peng-YM
020ba67bdb Removed test.js 2022-06-16 00:55:03 +08:00
Peng-YM
4a35f1293c Sub-Store 2.0 major release
- Used Peggy.js to replace the original parsers for Loon, QX and Surge.
- Added support for vmess + ws, vmess + http, snell, socks 5 parsing.
- Added various test cases for parsing.
2022-06-16 00:15:03 +08:00
Peng-YM
b9f4c2e596 Hot fix for Surge HTTP 2.0 feature 2022-06-15 23:36:23 +08:00
Peng-YM
1ba6211c7c Updated revert.js 2022-06-15 16:45:17 +08:00
Peng-YM
f0a286e516 Hot fix for Surge HTTP 2.0 feature 2022-06-14 23:52:08 +08:00
Peng-YM
f77aef65d8 Added global variables for Loon to ScriptOperator and ScriptFilter 2022-06-13 14:21:07 +08:00
NobyDa
8aa849312e Fix Surge TCP connection shortage. (#110) 2022-06-10 19:29:43 +08:00
Peng-YM
7e1de6e49f Fixed cloud artifact encode issue 2022-06-09 15:53:07 +08:00
Peng-YM
f6237c8f58 Fixed a bug when domain resolve failed, proxy server is missing 2022-06-07 16:56:52 +08:00
Peng-YM
63728b8423 Added sans-serif fonts 2022-06-07 16:45:23 +08:00
Peng-YM
9dea4800f7 Fixed emoji handling in fancy-characters.js 2022-06-07 16:37:10 +08:00
Peng-YM
9980101d2e Added ResolveDomainOperator 2022-06-07 15:55:34 +08:00
Peng-YM
6b9372ed37 Update geo.js (#106) 2022-06-04 13:40:55 +08:00
Peng-YM
6f4ca7f1b3 Refactored ProxyUtils and RuleUtils 2022-06-03 22:51:39 +08:00
Peng-YM
c78800b85d Added fancy-characters.js 2022-06-03 20:43:31 +08:00
Peng-YM
ecd33ef604 Replaced eval with Function for security 2022-06-03 20:36:06 +08:00
Peng-YM
5f4622c039 Now the "Script Operator" and "Script Filter" support async functions 2022-06-02 23:58:52 +08:00
Peng-YM
74e9087460 Updated QX configuration 2022-06-02 13:01:58 +08:00
Peng-YM
24a27e51e3 Added support for non-ascii characters in subscriptions, collections and artifacts 2022-05-30 13:47:59 +08:00
Peng-YM
d400be8b2e Added deprecation notification for Loon resource parser 2022-05-26 13:49:30 +08:00
Peng-YM
0185441ed2 Fixed Node.js cannot start issue 2022-05-26 11:17:36 +08:00
Peng-YM
f3b13a711e Fixed Surge https proxy issue 2022-05-25 23:02:06 +08:00
Peng-YM
f56c4358f9 Fixed local subscription not working in collections 2022-05-25 22:33:54 +08:00
Peng-YM
a9dc80cffa Minor fixes 2022-05-25 18:34:02 +08:00
Peng-YM
bbfd139ec8 Refactored Loon resource parser 2022-05-25 18:17:15 +08:00
Peng-YM
7b4f75fddc Added support for local subscription 2022-05-25 17:32:05 +08:00
Peng-YM
8c7e8f01ee Fixed express port issue 2022-05-25 14:42:26 +08:00
Peng-YM
a3ddb13289 Refine project structure 2022-05-25 14:33:46 +08:00
Peng-YM
3f6b1356cb Refactored OpenAPI 2022-05-25 11:27:53 +08:00
Peng-YM
fb660ce957 Minor changes 2022-05-25 11:00:00 +08:00
Peng-YM
b2987fa732 Updated scripts 2022-05-25 10:33:54 +08:00
Peng-YM
13dbe900fe Fixed ESLint issues 2022-05-24 22:24:31 +08:00
Peng-YM
3a1bc439d0 Added ESLint pipeline into Gulp 2022-05-24 22:19:03 +08:00
Peng-YM
605d211dbd Add nodemon for hot-reloading 2022-05-24 21:35:23 +08:00
Peng-YM
def4e496e4 Bump to ES6 2022-05-24 21:20:26 +08:00
Peng-YM
a24525c30c Auto darkmode 2022-05-24 17:05:56 +08:00
Peng-YM
9c30654d31 Add vmess ws obfs host script 2022-05-24 12:25:14 +08:00
Peng-YM
2da08c1817 Fixed collection ordering is not preserved 2022-05-24 11:31:54 +08:00
Peng-YM
693f23578e Fixed UA not working 2022-05-24 10:24:54 +08:00
Peng-YM
741121127b Update README.md 2022-05-23 18:47:34 +08:00
Peng-YM
8a031ec767 Modularized Sub-Store 2022-05-23 18:33:48 +08:00
Peng-YM
f926e0fe92 Removed airports configs 2022-05-23 12:24:12 +08:00
qwerzl
b3b1cdea38 把澳门加回来 (#96) 2022-05-20 15:30:10 +08:00
qwerzl
3ccd4cef52 国旗列表:添加了一些城市及其英文名 (#95) 2022-05-20 12:15:02 +08:00
Peng-YM
5279f86ebf Merge pull request #93 from qwerzl/master 2022-05-20 08:14:25 +08:00
qwerzl
24e4f8d37a Added “AE”, ”CA” for Canada and UAE flags 2022-05-20 07:36:39 +08:00
qwerzl
f84d3707bd Added “AE”, ”CA” for Canada and UAE flags 2022-05-19 23:38:44 +08:00
Peng-YM
8b4a440cdd Add logging for remote script 2022-05-18 19:20:13 +08:00
Peng-YM
2d4f589eb0 Added $argument for "ScriptFilter" and "ScriptOperator", closes #90
Example: https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/scripts/tls-fingerprint.js#fingerprint=67:1B:C8:F2:D4:20:EE:A7:EE:60:DA:BB:A3:F9:A4:D7:C8:29:0F:3E:2F:75:B6:A9:46:88:48:7D:D3:97:7E:98
2022-05-18 13:41:12 +08:00
Peng-YM
25cb0e7a69 Fixed cronSyncArtifact failed issue 2022-05-18 11:06:40 +08:00
Peng-YM
242ec3673d Reformat code 2022-05-17 10:24:05 +08:00
Peng-YM
caee36d818 Merge pull request #88 from Marsoit/patch-1 2022-05-16 16:27:13 +08:00
Jupiter
c8efa40d81 Update sub-store.js 2022-05-16 16:16:17 +08:00
Peng-YM
64e57bd784 Fixed cache bug 2022-05-14 14:37:50 +08:00
Peng-YM
b6c5f5ae05 Fixed cronSyncArtifacts error 2022-05-09 21:20:23 +08:00
Peng-YM
18851f182c Merge pull request #85 from xaver985/master 2022-05-09 15:11:48 +08:00
Peng-YM
8f7d58c5fb Update Loon.plugin 2022-05-09 11:16:55 +08:00
Xaver Wang
cbdf8f40e5 add flags 2022-05-09 02:47:23 +00:00
Xaver Wang
ff2efd2520 Update sub-store.js 2022-05-09 10:22:27 +08:00
Peng-YM
fd4ab77bf8 Update Loon.plugin 2022-05-05 10:57:56 +08:00
Peng-YM
6ef0e591d7 Merge pull request #83 from JOJOforshaun/patch-1 2022-05-05 08:54:06 +08:00
JOJOforshaun
cbd6311c2c Update README.md
添加小火箭配置说明。
2022-05-05 08:34:20 +08:00
Peng-YM
6ad3ce4a18 Added support for ShadowRocket, closes #82 2022-05-02 11:07:55 +08:00
Peng-YM
2cff94e85d Use cache in cronSyncArtifact to avoid downloading subscription multiple times 2022-05-02 11:07:55 +08:00
Peng-YM
7e20815bd9 Improve the performance of cronSyncArtifact 2022-04-30 11:29:13 +08:00
Peng-YM
95ce460c7d Forward subscription-userinfo from the first subscription in a collection 2022-04-28 17:03:01 +08:00
Peng-YM
0f99eb39cf Forward subscription-user-info header for Stash 2022-04-28 16:19:53 +08:00
Peng-YM
77e66ffe2a Fixed targetPlatform parameter not working on collections
Bug fixed
2022-04-18 16:13:37 +08:00
Peng-YM
869ca44da2 Updated tls-fingerprint.js using the new targetPlatform parameter 2022-04-17 23:34:26 +08:00
Peng-YM
c4605eb127 Added an additional parameter targetPlatform for Script Operator 2022-04-17 23:34:26 +08:00
Peng-YM
9619fda7f1 Update README.md 2022-04-01 20:04:33 +08:00
Peng-YM
4b0c1d9c70 Depreciated: removed cron-sync-artifacts.js
Synchronizing artifacts through cron script  is not longer supported, please use ShortCut app instead, see #46.
2022-04-01 20:02:07 +08:00
Peng-YM
c604cb6227 Merge pull request #77 from dompling/master 2022-04-01 19:58:11 +08:00
ShellManager
2f13938d1f 1.修复 mian.js App.vue 引入顺序,导致打包样式错乱问题
2.修复在本地运行或打包时出现 sass 警告
3. 注释了 app.scss 435 行的样式导致按钮背景色显示 #999
2022-03-31 16:14:20 +08:00
Peng-YM
0e953f0c47 Added an example of tls fingerprint configuration 2022-03-29 22:29:03 +08:00
Peng-YM
16a1ec5641 Fixes tfo issues 2022-03-29 15:30:18 +08:00
Peng-YM
0443a947d4 Added UDP relay support for the Trojan protocol on Surge 2022-03-26 21:49:29 +08:00
Peng-YM
dd0a9ff0c2 Bug fixes
1. 当 VMess 的 alterId 为 0 时,AEAD 会自动开启。
2. 更新了 Clash 的 VMess Websocket 参数写法,旧版本的写法(如 ws-path, ws-headers 等)已不被支持。
2022-03-25 22:54:42 +08:00
Peng-YM
2e75f172e4 Add UA auto detection for Stash iOS 2022-03-23 17:36:57 +08:00
Peng-YM
05aa163cd5 Update README.md 2022-03-22 16:00:07 +08:00
Peng-YM
9448cd2101 更新配置文档 2022-03-22 15:58:51 +08:00
Peng-YM
2a8f26c371 Merge pull request #76 from StashNetworks/master
add Stash for iOS
2022-03-22 15:57:57 +08:00
dev
8324d59fa4 add Stash for iOS 2022-03-21 22:47:42 +08:00
Peng-YM
e89506817d Add support for Loon UDP relay (SS/SSR/Trojan) 2022-03-11 23:05:20 +08:00
Peng-YM
f5cdb79446 Added support for Vmess AEAD (including Loon/QX/Surge), closes 70 2022-03-11 22:00:41 +08:00
Peng-YM
5ca585659a Added duplicate alert for collection, closes #74 2022-03-11 21:08:43 +08:00
Peng-YM
0cd1d02b0a Update README.md 2022-01-12 10:20:39 +08:00
Peng-YM
97291e4bbc Minified 2021-12-22 18:28:54 +08:00
Peng-YM
c47770d58a 使用 js-yaml 解析 Clash 配置以提升兼容性 2021-12-22 18:23:31 +08:00
Peng-YM
ca6a16ccf4 Merge pull request #61 from dompling/master
更新 sub-store.js 兼容 node 服务端
2021-10-24 14:26:38 +08:00
Peng-YM
31d8342f11 Merge branch 'master' into master 2021-10-24 14:26:29 +08:00
Peng-YM
8986cefb4b 新增国旗 (#58) 2021-10-24 14:22:13 +08:00
KotaHv
ff21c4f554 修复Loon http https生成相反的问题 (#62) 2021-10-23 21:19:27 +08:00
dompling
24173fd7fe nginx 2021-10-19 15:23:31 +08:00
dompling
af7e719cce sub-store 2021-10-18 14:40:40 +08:00
dompling
5edbffd1a6 nginx 服务器配置 2021-10-18 09:29:22 +08:00
dompling
bcfcd6d91d 更新 sub-store.js 兼容 node 服务端 2021-10-18 09:17:48 +08:00
Xaver Wang
cba24939ea 新增奇奇怪怪国家国旗 2021-10-11 21:47:41 +08:00
Xaver Wang
d39d3f6af0 Merge ; commit '638b510c7a812c3d8a79d8295a14c706b7d0d3b2' 2021-10-11 21:36:51 +08:00
Xaver Wang
d4c880e487 Merge branch 'master' of disabled:git@github.com:Peng-YM/Sub-Store.git 2021-10-11 21:36:15 +08:00
Xaver Wang
046b70a561 微调国旗 (#57) 2021-10-07 10:54:51 +08:00
Xaver Wang
496af15335 update 2021-10-07 09:55:33 +08:00
Xaver Wang
e5cfa2c821 Merge ; commit '5b1e24a4afdc1ca8d06293c172f7130a1c91a1fc'
Conflicts:
	backend/sub-store.min.js
2021-10-07 09:52:22 +08:00
Peng-YM
810f60f829 Added Minified File 2021-09-15 22:37:38 +08:00
Skyxim
baf462c222 订阅新增 User-Agent 选项,允许自定义 UA,默认使用 QuantumultX UA 以获取流量信息 2021-09-15 22:35:00 +08:00
Xaver Wang
5d8a337cda 添加几个国旗 (#54)
Many thanks for @xaver985
2021-09-14 16:58:44 +08:00
Xaver Wang
b9376e8ad6 fix 2021-09-14 16:53:04 +08:00
Xaver Wang
4c6a2a6118 update 2021-09-14 00:53:48 +08:00
Xaver Wang
8ea91879af update 2021-09-14 00:51:16 +08:00
Xaver Wang
6689f29281 update 2021-09-14 00:46:55 +08:00
Xaver Wang
b85a90562a update 2021-09-14 00:44:58 +08:00
Xaver Wang
f1431aa1e4 修复Trojan一处解析错误 2021-09-14 00:42:29 +08:00
Xaver Wang
d4110df6f2 Loon.plugin 2021-09-14 00:38:53 +08:00
Peng-YM
49ca638979 Add minified file 2021-09-09 18:35:43 +08:00
Skyxim
c31b2e703e 添加从Torjan Uri中获取sni值 (#52)
* 添加从Torjan Uri中获取sni值

感谢 Skyxim <skyxim@users.noreply.github.com>
2021-09-09 18:34:12 +08:00
Peng-YM
74daeb3035 添加 Surge Hybrid 参数支持,添加一键上传所有节点配置到 Gist 2021-09-03 18:05:22 +08:00
Matthew Wo
71d7f35b06 preserve ws-headers for vmess in Surge (#49) 2021-09-03 16:41:07 +08:00
Xaver Wang
c534a64ebc 新增部分国旗
感谢 @xaver985
2021-08-18 16:23:28 +08:00
Peng-YM
8c811ed474 新增节点去重操作,可以对重复节点进行删除或者自动重命名 2021-04-15 17:01:36 +08:00
Peng-YM
22767adb5a Reformat codes 2021-04-15 17:00:07 +08:00
Peng-YM
c8ef7533a8 Fixed Typo 2021-03-26 20:06:25 +08:00
Peng-YM
26f27d7296 增加 Loon TCP Fast Open (TFO) 选项 (SS/SSR) 的支持 2021-03-26 19:59:55 +08:00
Peng-YM
e9f29fec4a 增加 Loon UDP relay (SS/SSR) 的支持 2021-03-26 19:21:19 +08:00
Peng-YM
c5eb4878f2 增加 Loon UDP relay 的支持 2021-03-26 19:17:01 +08:00
Peng-YM
9060347fdd 由于QX脚本不走Rewrite,使用Backend方式触发 2021-02-27 14:47:38 +08:00
Peng-YM
4f3b4bfcee 多项改进
1. 现在可以编辑远程配置了。
2. 远程配置增加自动同步的选项,配合Cron脚本可以定期同步并上传配置到gist。
2021-02-27 13:00:32 +08:00
Peng-YM
299b287c22 Minor tweaks 2021-01-25 14:23:16 +08:00
Peng-YM
038141e5f2 Sub-Store现在会为单个订阅转发流量信息 2021-01-25 14:21:16 +08:00
Peng-YM
10102540ac Update README.md 2021-01-21 10:53:48 +08:00
Peng-YM
8da264608a 节点预览可以预览原始订阅的节点了 2021-01-04 00:03:43 +08:00
Peng-YM
37370cccda 修复Trojan一处解析错误 2020-12-31 14:30:01 +08:00
Peng-YM
4752438966 修复端口非443的Troajn节点无法解析的问题 2020-12-25 14:46:48 +08:00
Peng-YM
40da24a354 Update README.md 2020-12-17 14:52:41 +08:00
Peng-YM
3f3f9af66a 修复Base64预处理器未能正确处理某些订阅的问题 2020-12-16 17:21:57 +08:00
Peng-YM
b5d78225d2 Fixed a bug when script download failed 2020-12-15 16:26:11 +08:00
Peng-YM
199a81bfd8 修复上传时间错误的bug 2020-12-12 17:04:36 +08:00
Peng-YM
24cc4cbe8b 修复了Surge VMess解析WS header错误的问题,感谢@Paldies 2020-12-12 16:45:28 +08:00
Peng-YM
63f871ef0c Removed unused log 2020-12-12 16:11:28 +08:00
Peng-YM
943ce1b28f Update README.md 2020-12-12 11:54:56 +08:00
Peng-YM
e8234a75e3 Update README.md 2020-12-12 11:53:13 +08:00
Peng-YM
6f5837ce73 Update README.md 2020-12-12 11:52:36 +08:00
Peng-YM
40744a5bf6 Merge remote-tracking branch 'origin/master' 2020-12-12 11:29:47 +08:00
Peng-YM
1ab4f9c058 大量UI改进 2020-12-12 11:28:45 +08:00
fossabot
7cede18e85 Add license scan report and status (#26)
Signed off by: fossabot <badges@fossa.com>
2020-12-11 17:54:09 +08:00
Peng-YM
159047d589 夜间模式 2020-12-11 11:59:02 +08:00
Peng-YM
2eb3541462 修复正则排序不生效的bug 2020-12-11 10:26:57 +08:00
Peng-YM
b6dec7d509 修复一处typo 2020-12-10 22:16:55 +08:00
Peng-YM
943e75839d 正则表达式相关的节点操作支持使用(?i)指定忽略大小写匹配 2020-12-10 21:59:16 +08:00
Peng-YM
a04bfcc550 修复QX无法上传下载gist的bug 2020-12-09 14:14:25 +08:00
Peng-YM
d04a6edd7e 修复QX请求头没有Stringify导致请求超时的问题 2020-12-09 13:12:55 +08:00
Peng-YM
101036df84 修复列表图标闪烁问题 2020-12-09 11:17:42 +08:00
Peng-YM
a1e62f29be UI改进 2020-12-09 10:04:48 +08:00
Peng-YM
37062a38dd 修复UI不刷新的bug 2020-12-09 09:50:55 +08:00
Peng-YM
5176151938 Sub-Store 1.2 支持生成远程配置
现在允许用户生成配置并上传到Gist。
2020-12-08 20:19:46 +08:00
Peng-YM
8f6abcbd91 引入Artifacts相关API 2020-12-07 22:01:27 +08:00
Peng-YM
aacd3d3892 修复正则排序的bug 2020-12-05 15:46:44 +08:00
Peng-YM
4ce1a372af Merge branch 'dev' 2020-12-05 13:53:08 +08:00
Peng-YM
5aa9b8ceef Sub-Store 1.0版本
1. 移除了所有基于关键词的节点操作,统一使用基于正则表达式的节点操作。
2. UI的大量改进。
2020-12-05 13:39:11 +08:00
Peng-YM
9b4ae402bb Improved notifications 2020-12-03 15:47:32 +08:00
Peng-YM
6055c222ca Improved log 2020-12-02 16:35:54 +08:00
Peng-YM
ceae75379a 修正了几个订阅解析错误
- 修复了Quantmult格式VMess的obfs-path和obfs-header解析错误的问题。
- 修复了SSR URI格式protoparams和obfsparams解析错误的问题
2020-12-01 14:55:35 +08:00
Peng-YM
d856a422e5 修正规则解析错误,同时修复一处编码问题 2020-11-27 20:38:55 +08:00
Peng-YM
a094de32b5 修复分流解析错误的问题 2020-11-27 16:26:41 +08:00
Peng-YM
a064e0327d Loon Parser 2020-11-27 11:40:47 +08:00
Peng-YM
3d130a7cbf 修复QX IP-CIDR分流no-resolve解析不正确的问题 2020-11-26 19:59:27 +08:00
Peng-YM
d9a1830a30 添加一些内置分流 2020-11-26 19:50:54 +08:00
Peng-YM
23c3deb495 再次修复之前的bug 2020-11-26 15:24:30 +08:00
Peng-YM
d4966a793b 修复无法下载订阅的bug,我真是bug制造大师... 2020-11-26 15:16:26 +08:00
Peng-YM
abb7d61c72 修复一处错误的通知提示 2020-11-26 14:05:47 +08:00
Peng-YM
e3e5635514 添加Sub-Store分流支持 2020-11-26 13:43:54 +08:00
Peng-YM
84cd596a1c 修复备份错误的bug,请重新上传 2020-11-25 12:04:28 +08:00
Peng-YM
a066d95267 修复组合订阅节点处理错误的问题 2020-11-25 10:47:21 +08:00
Peng-YM
5baaf91ee7 修复Loon和Surge上无法保存的bug 2020-11-24 22:59:04 +08:00
Peng-YM
43d41270d1 修复无法保存创建订阅的bug 2020-11-24 22:29:23 +08:00
Peng-YM
f334f430d1 紧急修复BUG 2020-11-24 22:11:05 +08:00
Peng-YM
2f7023e9e6 组合订阅现在允许添加所有节点操作 2020-11-24 21:21:43 +08:00
Peng-YM
f2f72c696f 完全重构Sub-Store后端代码 2020-11-24 21:15:08 +08:00
Peng-YM
ff619f6040 修复QX VMess解析的一处错误 2020-11-09 19:50:02 +08:00
Peng-YM
4f2b5540cf Removed redudant comma 2020-11-02 14:27:23 +08:00
Peng-YM
06353a08ff Add timestamp for minified script 2020-10-27 20:26:21 +08:00
Peng-YM
54359b38dd Fixed Loon & Surge Bug 2020-10-27 20:09:48 +08:00
Peng-YM
c1a8b4ad5b Fixed Clash TLS host bug 2020-10-20 17:45:21 +08:00
Peng-YM
3b6a3e955c Fixed some default parameter errors 2020-10-20 17:19:54 +08:00
Peng-YM
30a452528a Fixed Clash plugin options parse error 2020-10-20 14:58:07 +08:00
omeks
4e6e863017 Fixed QX Producer Bug (#13)
Special thanks to @omeks
2020-10-14 10:27:17 +08:00
Peng-YM
393eb77fa9 组合订阅现在可以在链接后添加?target=Clash等指定输出的目标格式了 2020-10-10 16:42:33 +08:00
Peng-YM
6caaba35e4 Fixed Loon Bug 2020-10-06 11:30:39 +08:00
Peng-YM
fdf2d929f8 Try to fix loon bug 2020-10-06 10:54:02 +08:00
Peng-YM
e6cf086ef8 Add status codes for QX 2020-10-05 22:33:16 +08:00
Peng-YM
0ddc112453 Add status codes 2020-10-05 22:32:49 +08:00
Peng-YM
fbc3c4434a 添加跳转 2020-10-05 22:07:14 +08:00
Peng-YM
e44cf7c7f2 Delete backend for QX 2020-10-05 12:12:32 +08:00
Peng-YM
2f02c545be fixed revert 2020-10-04 20:24:30 +08:00
Peng-YM
6b9999da70 Add 2020-10-04 18:53:34 +08:00
Peng-YM
44b6505cfe 添加急救 2020-10-04 16:34:02 +08:00
Peng-YM
d058762b45 适配QX TF 390+特性 2020-10-04 10:37:04 +08:00
Peng-YM
e6288b1355 添加导入数据的接口 2020-10-03 21:32:23 +08:00
Peng-YM
f257c47fc1 add airport configs database 2020-10-03 21:21:38 +08:00
Peng-YM
c184c0ef96 添加导出所有数据的API接口 2020-10-03 21:06:03 +08:00
Peng-YM
8cf67a1aec 修复国旗优先级问题 2020-10-02 11:21:04 +08:00
Peng-YM
1e6f0d9694 Add Clash and Subscription producer 2020-09-29 17:09:29 +08:00
Peng-YM
755ac5565c Add some logs 2020-09-24 15:47:00 +08:00
Peng-YM
1717fee73e Minor bug fixed 2020-09-24 15:12:45 +08:00
Peng-YM
6d76cda39b 支持配置Gist同步 2020-09-24 14:59:14 +08:00
Peng-YM
8bae8e0ac5 Fixed single proxy display error 2020-09-23 18:00:04 +08:00
Peng-YM
ca31a50c48 Fixed whitespace issue for QX 2020-09-21 13:45:10 +08:00
Peng-YM
d81a0e2d3f Add script demo 2020-09-21 13:23:05 +08:00
Peng-YM
32595ee27e Fixed V2RayN SNI issue 2020-09-21 10:15:05 +08:00
Peng-YM
17d514c6da Bug fixed 2020-09-20 10:05:30 +08:00
Peng-YM
3c1e813f55 Bug fixed 2020-09-20 10:00:22 +08:00
Peng-YM
bb13f44fbd Added two examples for script operators 2020-09-20 09:56:40 +08:00
Peng-YM
cf8d4678ff Fixed scert for Surge 2020-09-19 16:59:22 +08:00
Peng-YM
ae6c720b59 添加导入导出配置功能 2020-09-18 12:56:37 +08:00
Peng-YM
bdf19c95f6 实现关键词排序UI 2020-09-18 11:07:23 +08:00
Peng-YM
28187d73f6 提供更方便的脚本操作接口 2020-09-15 11:13:56 +08:00
Peng-YM
512c3fffd5 Fixed Trojan issue 2020-09-14 10:12:53 +08:00
Peng-YM
3f0292e6ac Fixed Trojan server name bug 2020-09-14 09:44:00 +08:00
Peng-YM
ef3d2fc351 Merge remote-tracking branch 'origin/master' 2020-09-12 22:29:46 +08:00
Peng-YM
de6570cf91 添加Loon新UA判断 2020-09-12 22:29:18 +08:00
Peng-YM
e05bf0decd 添加Loon新UA判断 2020-09-12 22:28:11 +08:00
Peng-YM
c39fe4aac9 Delete sub.json 2020-09-12 11:17:35 +08:00
Peng-YM
b8960a99dd Delete collection.json 2020-09-12 11:17:26 +08:00
Peng-YM
173e9c05fd Minified backend 2020-09-09 20:30:25 +08:00
Peng-YM
8bc0c78cb8 Fixed Clash config memory issue 2020-09-09 19:51:04 +08:00
Peng-YM
fc6964359f Update README.md 2020-09-08 12:35:00 +08:00
Peng-YM
433ddf5c81 Update README.md 2020-09-08 12:32:16 +08:00
Peng-YM
5d101b0e8b UPDATE README 2020-09-06 20:57:21 +08:00
Peng-YM
6faa20eb3b Allow share QR Code 2020-09-06 20:53:11 +08:00
Peng-YM
82b3d71678 Update README.md 2020-09-06 11:54:27 +08:00
Peng-YM
b2f2697413 Merge pull request #2 from zZPiglet/master
尝试增加 SSD 支持。感谢Z神
2020-09-06 11:54:05 +08:00
Peng-YM
f1c64efb26 Fixed YAML parse 2020-09-06 11:53:07 +08:00
Peng-YM
0a3c1ec0a5 Supported URI Producer 2020-09-06 11:39:48 +08:00
zZPiglet
82b9efe1df 修正 ssd plugin 错误。 2020-09-06 02:13:03 +08:00
zZPiglet
ecaeabd4f4 修正 localhost。 2020-09-06 01:05:48 +08:00
zZPiglet
69c9d31012 尝试增加 SSD 支持。 2020-09-05 20:07:41 +08:00
Peng-YM
669cd60dc0 Bug fixed for Loon 2020-09-05 09:48:39 +08:00
Peng-YM
4e01d5030c Bug fixed 2020-09-04 21:15:26 +08:00
Peng-YM
04e7591d62 Add IP_API 2020-09-04 20:53:52 +08:00
Peng-YM
fc734ac268 Add scripts 2020-09-04 18:30:38 +08:00
Peng-YM
9acb381b24 Bug Fixed for reording multiple processors 2020-09-04 15:05:25 +08:00
Peng-YM
4af5c0c4ac Set UA to QX 2020-09-03 16:54:05 +08:00
Peng-YM
67fb47411f Added script filter and script opeartor 2020-09-03 16:16:28 +08:00
Peng-YM
6f8986fb48 Bug fixed for keyword replace 2020-09-03 12:54:34 +08:00
Peng-YM
62cdcc8fc4 Fixed Canada flag 2020-09-03 10:30:07 +08:00
Peng-YM
b751f2e719 UI Improvements 2020-09-03 10:23:50 +08:00
Peng-YM
85b02d8eb0 Minor Bug fixed 2020-09-01 20:24:32 +08:00
Peng-YM
9b39a9dfa4 bug fixed 2020-09-01 17:56:24 +08:00
Peng-YM
d60b7ff87c 修复缓存 2020-09-01 16:18:59 +08:00
Peng-YM
3d101cf22a 启用订阅缓存,大幅减少浏览器响应延迟 2020-09-01 12:08:14 +08:00
Peng-YM
f6f4467c07 添加四个重命名 2020-09-01 10:11:02 +08:00
Peng-YM
8e3afe82d3 修复过滤器切换模式不生效的bug 2020-09-01 08:43:30 +08:00
Peng-YM
37512eaa00 ... 2020-09-01 03:27:54 +08:00
Peng-YM
7b008ef684 Update RegionFilter.vue 2020-09-01 01:59:36 +08:00
Peng-YM
e493f7874a 修复过滤无效节点的bug 2020-09-01 00:39:15 +08:00
Peng-YM
001ce3f96f 添加正则过滤器,关键词过滤器 2020-08-31 23:57:34 +08:00
Peng-YM
a91d7bbfbb UI添加两个过滤器: 区域过滤器和类型过滤器 2020-08-31 21:10:36 +08:00
Peng-YM
20fac381af Update README.md 2020-08-31 19:19:59 +08:00
Peng-YM
5a30fb9549 修复SS URI的一处解析错误 2020-08-31 19:10:29 +08:00
Peng-YM
7565b02647 添加Quantumult格式的VMess订阅支持 2020-08-31 17:16:27 +08:00
Peng-YM
f793e21f17 修改UA为Quantumult,以获取流量信息等 2020-08-31 15:50:54 +08:00
Peng-YM
1b8c8599fa 修改UA为Quantumult,以获取流量信息等 2020-08-31 15:50:34 +08:00
Peng-YM
73c163098e 修复SS无混淆插件的情况 2020-08-31 15:09:53 +08:00
Peng-YM
bcbb18a07f Merge remote-tracking branch 'origin/master' 2020-08-31 14:53:34 +08:00
Peng-YM
01dd6c852e 修复obfs-local格式SS订阅 2020-08-31 14:53:13 +08:00
Peng-YM
87e5671a6f Update Surge.sgmodule 2020-08-31 14:05:53 +08:00
Peng-YM
62bf30db08 处理Option请求 2020-08-31 14:02:56 +08:00
Peng-YM
c066091b2d Surge iOS已修复,感谢@pysta大佬协助 2020-08-31 13:44:00 +08:00
Peng-YM
a5a5415fc0 Adapt status code 2020-08-31 13:41:39 +08:00
Peng-YM
43e9ef4a6f Try to fix QX 2020-08-31 13:09:13 +08:00
Peng-YM
acc637a3bf Merge remote-tracking branch 'origin/master' 2020-08-31 12:27:05 +08:00
Peng-YM
42f61069dd 修复过滤器逻辑错误 2020-08-31 12:26:30 +08:00
Peng-YM
f8bd4f6713 Update sub-store.js 2020-08-31 03:27:27 +08:00
Peng-YM
d6c438514b Update Surge.sgmodule 2020-08-31 02:50:33 +08:00
Peng-YM
86c3abd11d Merge ; commit '47d06919aa1524905cb68fcab67f5b8968e5d0d1'
Conflicts:
	config/Surge.sgmodule
2020-08-31 02:44:46 +08:00
Peng-YM
d0b3a26d2d Update Surge.sgmodule 2020-08-31 02:42:15 +08:00
Peng-YM
ee55ae887f Update Surge.sgmodule 2020-08-31 02:19:04 +08:00
Peng-YM
30bfd1d522 Merge remote-tracking branch 'origin/master' 2020-08-31 02:15:43 +08:00
Peng-YM
e1717459d4 Change Domain 2020-08-31 02:14:45 +08:00
Peng-YM
d3a4692a6a Update Surge.sgmodule 2020-08-31 02:05:39 +08:00
Peng-YM
0dfadd86c2 修改域名为sub.com 2020-08-31 01:08:49 +08:00
Peng-YM
2a7466bba8 更换域名 2020-08-31 00:59:47 +08:00
Peng-YM
2869e2479f QX header修复 2020-08-31 00:57:06 +08:00
Peng-YM
9bfb0e6430 [Bug Fixed] 修复Loon VMess错误 2020-08-31 00:37:11 +08:00
Peng-YM
3a5e043f80 Update README.md 2020-08-31 00:28:00 +08:00
Peng-YM
e66ce0769b Update README.md 2020-08-31 00:27:48 +08:00
Peng-YM
21e7523457 [Bug Fixed] 修复Loon VMess错误 2020-08-31 00:15:48 +08:00
Peng-YM
f62959c5d1 Add more timeout to axios 2020-08-30 21:58:31 +08:00
Peng-YM
e6cb50ee47 Add config for Surge and Loon 2020-08-30 21:52:17 +08:00
Peng-YM
942acfcf19 Bug fixed 2020-08-30 21:45:31 +08:00
Peng-YM
9edbcb35d7 Alpha test 2020-08-30 21:25:46 +08:00
Peng-YM
50d92ad971 Working on sub-editor 2020-08-28 15:00:45 +08:00
Peng-YM
c2999826c7 适配移动端web app
样式来自Chavyleung
2020-08-26 21:33:00 +08:00
Peng-YM
6d40d01c5e Merge remote-tracking branch 'origin/master' 2020-08-26 21:10:25 +08:00
Peng-YM
9f6fe2336b Add cache 2020-08-26 21:09:34 +08:00
Peng-YM
c05373b1c9 Update README.md 2020-08-25 15:15:01 +08:00
Peng-YM
c908e6b190 Update README.md 2020-08-25 11:25:34 +08:00
Peng-YM
82fc7e4f0f Update README.md 2020-08-25 11:21:52 +08:00
Peng-YM
43998f9274 支持Clash YAML输入 2020-08-25 11:18:35 +08:00
Peng-YM
faab7338d5 Add some components 2020-08-23 12:25:02 +08:00
Peng-YM
b0aca387bb Added subscription page 2020-08-23 00:56:35 +08:00
Peng-YM
3657eddf35 Update README.md 2020-08-21 18:12:29 +08:00
Peng-YM
a378d3e73a Support remote script 2020-08-21 18:06:27 +08:00
Peng-YM
512e49915c Merge branch 'master' of https://github.com/Peng-YM/Sub-Store into master 2020-08-19 21:34:44 +08:00
Peng-YM
d71fb0a1cf Web 2020-08-19 21:34:35 +08:00
Peng-YM
7c4a261104 Update README.md 2020-08-19 19:31:15 +08:00
Peng-YM
02e3246388 Update README.md 2020-08-19 19:30:30 +08:00
Peng-YM
fe2f37984f Added vuetify 2020-08-19 16:36:01 +08:00
Peng-YM
d7ac5d4a1d Re-organized project structure 2020-08-19 16:18:48 +08:00
Peng-YM
ff12e4ef16 Create LICENSE 2020-08-19 16:16:07 +08:00
55 changed files with 1928 additions and 847 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store
# json config
sub-store.json
sub-store_*.json
root.json
# Logs

0
.gitmodules vendored
View File

View File

@@ -26,13 +26,14 @@ Core functionalities:
### Supported Input Formats
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs.
> ⚠️ Do not use `Shadowrocket` or `NekoBox` to export URI and then import it as input. The URIs exported in this way may not be standard URIs. However, we have already supported some very common non-standard URIs (such as VMess, VLESS).
- [x] Proxy URI Scheme(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
example: `socks5+tls://user:pass@ip:port#name`
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
> Please note, HTTP(s) does not have a standard URI format, so it is not supported. Please use other formats.
- [x] Clash Proxies YAML
- [x] Clash Proxy JSON(single line)
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
@@ -41,6 +42,9 @@ Core functionalities:
- [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, AnyTLS)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
Deprecated(The frontend doesn't show it, but the backend still supports it, with the query parameter `target=Clash`):
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
### Supported Target Platforms
@@ -48,7 +52,6 @@ Core functionalities:
- [x] Plain JSON
- [x] Stash
- [x] Clash.Meta(mihomo)
- [x] Clash
- [x] Surfboard
- [x] Surge
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
@@ -60,6 +63,10 @@ Core functionalities:
- [x] V2Ray
- [x] V2Ray URI
Deprecated:
- [x] Clash
## 2. Subscription Formatting
### Filtering
@@ -92,22 +99,16 @@ Go to `backend` directories, install node dependencies:
pnpm i
```
1. In `backend`, run the backend server on http://localhost:3000
babel(old school)
```
pnpm start
```
or
esbuild(experimental)
```
SUB_STORE_BACKEND_API_PORT=3000 pnpm run --parallel "/^dev:.*/"
```
### Build
```
pnpm bundle:esbuild
```
## LICENSE
This project is under the GPL V3 LICENSE.
@@ -122,3 +123,9 @@ This project is under the GPL V3 LICENSE.
- Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!
- Special thanks to @Orz-3 and @58xinian for their awesome icons.
## Sponsors
[![image](./support.nodeseek.com_page_promotion_id=8.png)](https://yxvm.com)
[NodeSupport](https://github.com/NodeSeekDev/NodeSupport) sponsored this project.

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.17.8",
"version": "2.20.2",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
"main": "src/main.js",
"scripts": {
@@ -17,6 +17,11 @@
},
"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",
@@ -25,17 +30,21 @@
"connect-history-api-fallback": "^2.0.0",
"cron": "^3.1.6",
"dns-packet": "^5.6.1",
"dotenv": "^16.4.7",
"express": "^4.17.1",
"http-proxy-middleware": "^2.0.6",
"fetch-socks": "^1.3.2",
"http-proxy-middleware": "^3.0.3",
"ip-address": "^9.0.5",
"js-base64": "^3.7.2",
"json5": "^2.2.3",
"jsrsasign": "^11.1.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"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--;
}

330
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:
.:
@@ -29,39 +34,51 @@ importers:
dns-packet:
specifier: ^5.6.1
version: 5.6.1
dotenv:
specifier: ^16.4.7
version: 16.5.0
express:
specifier: ^4.17.1
version: 4.21.2
fetch-socks:
specifier: ^1.3.2
version: 1.3.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
js-base64:
specifier: ^3.7.2
version: 3.7.7
json5:
specifier: ^2.2.3
version: 2.2.3
jsrsasign:
specifier: ^11.1.0
version: 11.1.0
lodash:
specifier: ^4.17.21
version: 4.17.21
mime-types:
specifier: ^2.1.35
version: 2.1.35
ms:
specifier: ^2.1.3
version: 2.1.3
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 +1089,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 +1114,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 +1126,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 +1173,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 +1329,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 +1409,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 +1530,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 +1624,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'}
@@ -1680,6 +1667,10 @@ packages:
resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
engines: {node: '>=0.4', npm: '>=1.2'}
dotenv@16.5.0:
resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1702,9 +1693,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==}
@@ -1989,6 +1977,9 @@ packages:
fastq@1.18.0:
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
fetch-socks@1.3.2:
resolution: {integrity: sha512-vkH5+Zgj2yEbU57Cei0iyLgTZ4OkEKJj56Xu3ViB5dpsl599JgEooQ3x6NVagIFRHWnWJ+7K0MO0aIV1TMgvnw==}
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -2074,16 +2065,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 +2151,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 +2254,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 +2338,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 +2560,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 +2596,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 +2647,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 +2661,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 +2686,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 +2695,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 +3008,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 +3212,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 +3297,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 +3324,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 +3454,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'}
@@ -3717,6 +3642,10 @@ packages:
resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==}
engines: {node: '>=0.10.0'}
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
snapdragon-node@2.1.1:
resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
engines: {node: '>=0.10.0'}
@@ -3729,6 +3658,10 @@ packages:
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
engines: {node: '>=0.10.0'}
socks@2.8.6:
resolution: {integrity: sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
source-map-generator@0.8.0:
resolution: {integrity: sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==}
engines: {node: '>= 10'}
@@ -3790,11 +3723,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 +3938,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 +4030,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 +5298,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 +5322,6 @@ snapshots:
dependencies:
async-done: 1.3.2
asynckit@0.4.0: {}
atob@2.1.2: {}
automerge@1.0.1-preview.7:
@@ -5418,13 +5334,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 +5403,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 +5687,6 @@ snapshots:
caniuse-lite@1.0.30001692: {}
caseless@0.12.0: {}
chai@4.5.0:
dependencies:
assertion-error: 1.1.0
@@ -5908,10 +5814,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 +5952,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 +6042,6 @@ snapshots:
defined@1.0.1: {}
delayed-stream@1.0.0: {}
depd@2.0.0: {}
deps-sort@2.0.1:
@@ -6188,6 +6084,8 @@ snapshots:
domain-browser@1.2.0: {}
dotenv@16.5.0: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.1
@@ -6223,11 +6121,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: {}
@@ -6674,6 +6567,11 @@ snapshots:
dependencies:
reusify: 1.0.4
fetch-socks@1.3.2:
dependencies:
socks: 2.8.6
undici: 7.4.0
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@@ -6767,7 +6665,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 +6679,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 +6768,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 +6948,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 +7031,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 +7258,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 +7296,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 +7333,6 @@ snapshots:
isobject@3.0.1: {}
isstream@0.1.2: {}
js-base64@3.7.7: {}
js-tokens@4.0.0: {}
@@ -7476,8 +7346,6 @@ snapshots:
dependencies:
argparse: 2.0.1
jsbn@0.1.1: {}
jsbn@1.1.0: {}
jsesc@3.0.2: {}
@@ -7492,19 +7360,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 +7748,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 +7961,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 +8020,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 +8054,6 @@ snapshots:
dependencies:
side-channel: 1.1.0
qs@6.5.3: {}
querystring-es3@0.2.1: {}
queue-microtask@1.2.3: {}
@@ -8374,29 +8223,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: {}
@@ -8617,6 +8443,8 @@ snapshots:
slash@1.0.0: {}
smart-buffer@4.2.0: {}
snapdragon-node@2.1.1:
dependencies:
define-property: 1.0.0
@@ -8640,6 +8468,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
socks@2.8.6:
dependencies:
ip-address: 9.0.5
smart-buffer: 4.2.0
source-map-generator@0.8.0: {}
source-map-resolve@0.5.3:
@@ -8694,18 +8527,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 +8814,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 +8826,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 +8947,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

@@ -1,7 +1,8 @@
import { Base64 } from 'js-base64';
import { Buffer } from 'buffer';
import rs from '@/utils/rs';
import YAML from '@/utils/yaml';
import download from '@/utils/download';
import download, { downloadFile } from '@/utils/download';
import {
isIPv4,
isIPv6,
@@ -24,6 +25,7 @@ import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
import Gist from '@/utils/gist';
import { isPresent } from './producers/utils';
import { doh } from '@/utils/dns';
import JSON5 from 'json5';
function preprocess(raw) {
for (const processor of PROXY_PREPROCESSORS) {
@@ -114,12 +116,7 @@ async function processFn(
if (item.type.indexOf('Script') !== -1) {
const { mode, content } = item.args;
if (mode === 'link') {
let noCache;
let url = content || '';
if (url.endsWith('#noCache')) {
url = url.replace(/#noCache$/, '');
noCache = true;
}
// extract link arguments
const rawArgs = url.split('#');
if (rawArgs.length > 1) {
@@ -138,10 +135,17 @@ async function processFn(
}
}
}
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
const downloadUrlMatch = url.match(
/^\/api\/(file|module)\/(.+)/,
);
url = `${url.split('#')[0]}${
rawArgs[2]
? `#${rawArgs[2]}`
: $arguments?.noCache != null ||
$arguments?.insecure != null
? `#${rawArgs[1]}`
: ''
}`;
const downloadUrlMatch = url
.split('#')[0]
.match(/^\/api\/(file|module)\/(.+)/);
if (downloadUrlMatch) {
let type = '';
try {
@@ -171,6 +175,17 @@ async function processFn(
);
throw new Error(`无法加载 ${type}: ${url}`);
}
} else if (url?.startsWith('/')) {
try {
const fs = eval(`require("fs")`);
script = fs.readFileSync(url.split('#')[0], 'utf8');
// $.info(`Script loaded: >>>\n ${script}`);
} catch (err) {
$.error(
`Error when reading local script: ${item.args.content}.\n Reason: ${err}`,
);
throw new Error(`无法从该路径读取脚本文件: ${url}`);
}
} else {
// if this is a remote script, download it
try {
@@ -328,8 +343,12 @@ export const ProxyUtils = {
MMDB,
Gist,
download,
downloadFile,
isValidUUID,
doh,
Buffer,
Base64,
JSON5,
};
function tryParse(parser, line) {
@@ -518,6 +537,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 (
@@ -605,7 +632,7 @@ function lastParse(proxy) {
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
}
if (
['shadowsocks'].includes(proxy.type) &&
['ss'].includes(proxy.type) &&
isPresent(proxy, 'shadow-tls-password')
) {
proxy.plugin = 'shadow-tls';

View File

@@ -12,6 +12,8 @@ import getLoonParser from './peggy/loon';
import getQXParser from './peggy/qx';
import getTrojanURIParser from './peggy/trojan-uri';
import $ from '@/core/app';
import JSON5 from 'json5';
import YAML from '@/utils/yaml';
import { Base64 } from 'js-base64';
@@ -128,13 +130,13 @@ function URI_SS() {
// parse url
let content = line.split('ss://')[1];
let name = line.split('#')[1];
const proxy = {
name: decodeURIComponent(line.split('#')[1]),
type: 'ss',
};
content = content.split('#')[0]; // strip proxy name
// handle IPV4 and IPV6
let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
let serverAndPortArray = content.match(/@([^/?]*)(\/|\?|$)/);
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
let userInfoStr;
@@ -260,6 +262,10 @@ function URI_SS() {
if (/(&|\?)tfo=(1|true)/i.test(query)) {
proxy.tfo = true;
}
if (name != null) {
name = decodeURIComponent(name);
}
proxy.name = name ?? `SS ${proxy.server}:${proxy.port}`;
return proxy;
};
return { name, test, parse };
@@ -342,7 +348,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());
@@ -435,7 +441,16 @@ function URI_VMess() {
type: 'vmess',
server,
port,
cipher: getIfPresent(params.scy, 'auto'),
// https://github.com/2dust/v2rayN/wiki/Description-of-VMess-share-link
// https://github.com/XTLS/Xray-core/issues/91
cipher: [
'auto',
'aes-128-gcm',
'chacha20-poly1305',
'none',
].includes(params.scy)
? params.scy
: 'auto',
uuid: params.id,
alterId: parseInt(
getIfPresent(params.aid ?? params.alterId, 0),
@@ -452,8 +467,12 @@ function URI_VMess() {
);
}
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
if (proxy.tls && params.sni && params.sni !== '') {
proxy.sni = params.sni;
if (proxy.tls) {
if (params.sni && params.sni !== '') {
proxy.sni = params.sni;
} else if (params.peer && params.peer !== '') {
proxy.sni = params.peer;
}
}
let httpupgrade = false;
// handle obfs
@@ -465,8 +484,8 @@ function URI_VMess() {
['http'].includes(params.type)
) {
proxy.network = 'http';
} else if (['grpc'].includes(params.net)) {
proxy.network = 'grpc';
} else if (['grpc', 'kcp', 'quic'].includes(params.net)) {
proxy.network = params.net;
} else if (
params.net === 'httpupgrade' ||
proxy.network === 'httpupgrade'
@@ -492,6 +511,11 @@ function URI_VMess() {
} catch (e) {}
let transportPath = params.path;
// 补上默认 path
if (['ws'].includes(proxy.network)) {
transportPath = transportPath || '/';
}
if (proxy.network === 'http') {
if (transportHost) {
// 1)http(tcp)->host中间逗号(,)隔开
@@ -511,13 +535,28 @@ function URI_VMess() {
}
}
// 传输层应该有配置, 暂时不考虑兼容不给配置的节点
if (transportPath || transportHost) {
if (
transportPath ||
transportHost ||
['kcp', 'quic'].includes(proxy.network)
) {
if (['grpc'].includes(proxy.network)) {
proxy[`${proxy.network}-opts`] = {
'grpc-service-name': getIfNotBlank(transportPath),
'_grpc-type': getIfNotBlank(params.type),
'_grpc-authority': getIfNotBlank(params.authority),
};
} else if (['kcp', 'quic'].includes(proxy.network)) {
proxy[`${proxy.network}-opts`] = {
[`_${proxy.network}-type`]: getIfNotBlank(
params.type,
),
[`_${proxy.network}-host`]: getIfNotBlank(
getIfNotBlank(transportHost),
),
[`_${proxy.network}-path`]:
getIfNotBlank(transportPath),
};
} else {
const opts = {
path: getIfNotBlank(transportPath),
@@ -533,6 +572,12 @@ function URI_VMess() {
delete proxy.network;
}
}
proxy['client-fingerprint'] = params.fp;
proxy.alpn = params.alpn ? params.alpn.split(',') : undefined;
// 然而 wiki 和 app 实测中都没有字段表示这个
// proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.allowInsecure);
return proxy;
}
};
@@ -634,6 +679,9 @@ function URI_VLESS() {
}
if (!proxy.network && isShadowrocket && params.obfs) {
proxy.network = params.obfs;
if (['none'].includes(proxy.network)) {
proxy.network = 'tcp';
}
}
if (['websocket'].includes(proxy.network)) {
proxy.network = 'ws';
@@ -736,6 +784,8 @@ function URI_AnyTLS() {
proxy[key] = value ? value.split(',') : undefined;
} else if (['insecure'].includes(key)) {
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
} else if (['udp'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value);
} else {
proxy[key] = value;
}
@@ -954,6 +1004,9 @@ function URI_TUIC() {
proxy.tfo = true;
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
proxy[key] = /(TRUE)|1/i.test(value);
} else if (key === 'congestion-control') {
proxy['congestion-controller'] = value;
delete proxy[key];
} else {
proxy[key] = value;
}
@@ -1078,15 +1131,21 @@ function URI_Trojan() {
function Clash_All() {
const name = 'Clash Parser';
const test = (line) => {
let proxy;
try {
JSON.parse(line);
proxy = JSON5.parse(line);
} catch (e) {
return false;
proxy = YAML.parse(line);
}
return true;
return !!proxy?.type;
};
const parse = (line) => {
const proxy = JSON.parse(line);
let proxy;
try {
proxy = JSON5.parse(line);
} catch (e) {
proxy = YAML.parse(line);
}
if (
![
'anytls',

View File

@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy;
}
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
proxy.type = "ssr";
// handle ssr obfs
proxy.obfs = obfs.type;
}
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)* {
proxy.type = "ss";
// handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path);
}
}
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0;
handleTransport();
}
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
proxy.type = "vless";
handleTransport();
}
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "trojan";
handleTransport();
}
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
proxy.type = "hysteria2";
}
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http";
proxy.tls = true;
}
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http";
}
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "socks5";
}
@@ -175,11 +175,16 @@ shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-s
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_name = comma sni:("tls-name") equals host:domain { proxy.sni = host; }
sni = comma sni:("sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
@@ -188,6 +193,8 @@ ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _
equals = _ "=" _

View File

@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy;
}
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)*{
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
proxy.type = "ssr";
// handle ssr obfs
proxy.obfs = obfs.type;
}
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/others)* {
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)* {
proxy.type = "ss";
// handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") {
@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path);
}
}
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/others)* {
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0;
handleTransport();
}
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
proxy.type = "vless";
handleTransport();
}
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "trojan";
handleTransport();
}
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/others)* {
hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
proxy.type = "hysteria2";
}
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http";
proxy.tls = true;
}
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/others)* {
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http";
}
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/others)* {
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "socks5";
}
@@ -173,11 +173,16 @@ shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-s
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_name = comma sni:("tls-name") equals host:domain { proxy.sni = host; }
sni = comma sni:("sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
short_id = comma "short-id" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["short-id"] = match.join("").replace(/^"(.*)"$/, '$1'); }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
ip_mode = comma "ip-mode" equals match:[^,]+ { proxy["ip-version"] = match.join(""); }
@@ -186,6 +191,8 @@ ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
salamander_password = comma "salamander-password" equals match:[^,]+ { proxy['obfs-password'] = match.join(""); proxy.obfs = 'salamander'; }
block_quic = comma "block-quic" equals flag:bool { if(flag) proxy["block-quic"] = "on"; else proxy["block-quic"] = "off"; }
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
comma = _ "," _
equals = _ "=" _

View File

@@ -49,7 +49,7 @@ trojan = "trojan" equals address
}
shadowsocks = "shadowsocks" equals address
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* {
if (proxy.protocol || proxy.type === "ssr") {
proxy.type = "ssr";
if (!proxy.protocol) {
@@ -86,10 +86,10 @@ vmess = "vmess" equals address
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
proxy.alterId = 0;
if (proxy.aead === false) {
proxy.alterId = 1;
} else {
proxy.alterId = proxy.alterId || 0;
proxy.alterId = 0;
}
handleObfs();
}
@@ -145,18 +145,20 @@ port = digits:[0-9]+ {
}
}
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
method = comma "method" equals cipher:cipher {
proxy.cipher = cipher;
};
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }

View File

@@ -47,7 +47,7 @@ trojan = "trojan" equals address
}
shadowsocks = "shadowsocks" equals address
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp_new/fast_open/tag/server_check_url/others)* {
if (proxy.protocol || proxy.type === "ssr") {
proxy.type = "ssr";
if (!proxy.protocol) {
@@ -84,10 +84,10 @@ vmess = "vmess" equals address
(uuid/method/over_tls/tls_host/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/server_check_url/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
if (proxy.aead) {
proxy.alterId = 0;
if (proxy.aead === false) {
proxy.alterId = 1;
} else {
proxy.alterId = proxy.alterId || 0;
proxy.alterId = 0;
}
handleObfs();
}
@@ -143,18 +143,20 @@ port = digits:[0-9]+ {
}
}
username = comma "username" equals username:[^=,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^=,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
username = comma "username" equals username:[^,]+ { proxy.username = username.join("").trim(); }
password = comma "password" equals password:[^,]+ { proxy.password = password.join("").trim(); }
uuid = comma "password" equals uuid:[^,]+ { proxy.uuid = uuid.join("").trim(); }
method = comma "method" equals cipher:cipher {
proxy.cipher = cipher;
};
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
udp_over_tcp = comma "udp-over-tcp" equals flag:bool { throw new Error("UDP over TCP is not supported"); }
udp_over_tcp_new = comma "udp-over-tcp" equals param:$[^=,]+ { if (param === "sp.v1") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 1; } else if (param === "sp.v2") { proxy["udp-over-tcp"] = true; proxy["udp-over-tcp-version"] = 2; } else if (param === "true") { proxy["_ssr_python_uot"] = true; } else { throw new Error("Invalid value for udp-over-tcp"); } }
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }

View File

@@ -55,10 +55,11 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
if (proxy.aead) {
proxy.alterId = 0;
} else {
proxy.alterId = proxy.alterId || 0;
proxy.alterId = 1;
}
handleWebsocket();
handleShadowTLS();
@@ -104,11 +105,11 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
proxy.type = "wireguard-surge";
handleShadowTLS();
}
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/fast_open/tfo/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
proxy.type = "hysteria2";
handleShadowTLS();
}
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "socks5";
handleShadowTLS();
}
@@ -120,7 +121,6 @@ socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek pas
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/block_quic/others)* {
proxy.type = "direct";
}
address = comma server:server comma port:port {
proxy.server = server;
proxy.port = port;
@@ -178,8 +178,8 @@ username = & {
peg$currPos = end;
return true;
}
} { proxy.username = $.username; }
password = comma match:[^,]+ { proxy.password = match.join(""); }
} { proxy.username = $.username.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
sni = comma "sni" equals sni:("off"/domain) {
@@ -195,7 +195,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
@@ -211,11 +211,11 @@ ws_headers = comma "ws-headers" equals headers:$[^,]+ {
const result = {};
pairs.forEach(pair => {
const [key, value] = pair.trim().split(":");
result[key.trim()] = value.trim();
result[key.trim()] = value.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1');
})
obfs["ws-headers"] = result;
}
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
ws_path = comma "ws-path" equals path:uri { obfs.path = path.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };

View File

@@ -53,10 +53,11 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none";
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
if (proxy.aead) {
proxy.alterId = 0;
} else {
proxy.alterId = proxy.alterId || 0;
proxy.alterId = 1;
}
handleWebsocket();
handleShadowTLS();
@@ -175,8 +176,8 @@ username = & {
peg$currPos = end;
return true;
}
} { proxy.username = $.username; }
password = comma match:[^,]+ { proxy.password = match.join(""); }
} { proxy.username = $.username.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
password = comma match:[^,]+ { proxy.password = match.join("").replace(/^"(.*)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
sni = comma "sni" equals sni:("off"/domain) {
@@ -192,7 +193,7 @@ tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:
snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
@@ -208,11 +209,11 @@ ws_headers = comma "ws-headers" equals headers:$[^,]+ {
const result = {};
pairs.forEach(pair => {
const [key, value] = pair.trim().split(":");
result[key.trim()] = value.trim();
result[key.trim()] = value.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1');
})
obfs["ws-headers"] = result;
}
ws_path = comma "ws-path" equals path:uri { obfs.path = path; }
ws_path = comma "ws-path" equals path:uri { obfs.path = path.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };

View File

@@ -50,6 +50,26 @@ function Base64Encoded() {
return { name, test, parse };
}
function fallbackBase64Encoded() {
const name = 'Fallback Base64 Pre-processor';
const test = function (raw) {
return true;
};
const parse = function (raw) {
const decoded = Base64.decode(raw);
if (!/^\w+(:\/\/|\s*?=\s*?)\w+/m.test(decoded)) {
$.error(
`Fallback Base64 Pre-processor error: decoded line does not start with protocol`,
);
return raw;
}
return decoded;
};
return { name, test, parse };
}
function Clash() {
const name = 'Clash Pre-processor';
const test = function (raw) {
@@ -75,6 +95,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}"`;
}
@@ -161,4 +183,11 @@ function FullConfig() {
return { name, test, parse };
}
export default [HTML(), Clash(), Base64Encoded(), SSD(), FullConfig()];
export default [
HTML(),
Clash(),
Base64Encoded(),
SSD(),
FullConfig(),
fallbackBase64Encoded(),
];

View File

@@ -284,7 +284,15 @@ function SortOperator(order = 'asc') {
}
// sort by regex
function RegexSortOperator(expressions) {
function RegexSortOperator(input) {
const order = input.order || 'asc';
let expressions = input.expressions;
if (Array.isArray(input)) {
expressions = input;
}
if (!Array.isArray(expressions)) {
expressions = [];
}
return {
name: 'Regex Sort Operator',
func: (proxies) => {
@@ -295,8 +303,13 @@ function RegexSortOperator(expressions) {
if (oA && !oB) return -1;
if (oB && !oA) return 1;
if (oA && oB) return oA < oB ? -1 : 1;
if ((!oA && !oB) || (oA && oB && oA === oB))
return a.name < b.name ? -1 : 1; // fallback to normal sort
if (order === 'original') {
return 0;
} else if (order === 'desc') {
return a.name < b.name ? 1 : -1;
} else {
return a.name < b.name ? -1 : 1;
}
});
},
};
@@ -610,9 +623,11 @@ const DOMAIN_RESOLVERS = {
const cached = resourceCache.get(id);
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `http://223.6.6.6/resolve?edns_client_subnet=${edns}/24&name=${encodeURIComponent(
domain,
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
url: `http://223.6.6.6/resolve?edns_client_subnet=${edns}/${
isIPv4(edns) ? 24 : 56
}&name=${encodeURIComponent(domain)}&type=${
type === 'IPv6' ? 'AAAA' : 'A'
}&short=1`,
headers: {
accept: 'application/dns-json',
},
@@ -1128,6 +1143,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
'$httpClient',
'$notification',
'ProxyUtils',
'yaml',
'Buffer',
'b64d',
'b64e',
'scriptResourceCache',
'flowUtils',
'produceArtifact',
@@ -1145,6 +1164,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
// eslint-disable-next-line no-undef
$notification,
ProxyUtils,
ProxyUtils.yaml,
ProxyUtils.Buffer,
ProxyUtils.Base64.decode,
ProxyUtils.Base64.encode,
scriptResourceCache,
flowUtils,
produceArtifact,
@@ -1157,6 +1180,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
'$substore',
'lodash',
'ProxyUtils',
'yaml',
'Buffer',
'b64d',
'b64e',
'scriptResourceCache',
'flowUtils',
'produceArtifact',
@@ -1168,6 +1195,10 @@ function createDynamicFunction(name, script, $arguments, $options) {
$,
lodash,
ProxyUtils,
ProxyUtils.yaml,
ProxyUtils.Buffer,
ProxyUtils.Base64.decode,
ProxyUtils.Base64.encode,
scriptResourceCache,
flowUtils,
produceArtifact,

View File

@@ -1,4 +1,5 @@
import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Clash_Producer() {
const type = 'ALL';
@@ -40,12 +41,17 @@ export default function Clash_Producer() {
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
].includes(proxy.cipher)) ||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
(proxy.type === 'snell' && proxy.version >= 4) ||
(proxy.type === 'vless' &&
(typeof proxy.flow !== 'undefined' ||
proxy['reality-opts']))
) {
return false;
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
$.error(
`Clash 不支持前置代理字段. 已过滤节点 ${proxy.name}`,
);
return false;
}
return true;
})
@@ -128,6 +134,18 @@ export default function Clash_Producer() {
proxy['h2-opts'].headers.host = [host];
}
}
if (proxy.network === 'ws') {
const wsPath = proxy['ws-opts']?.path;
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
// eslint-disable-next-line no-unused-vars
const [_, path = '', ed = ''] = reg.exec(wsPath);
proxy['ws-opts'].path = path;
if (ed !== '') {
proxy['ws-opts']['early-data-header-name'] =
'Sec-WebSocket-Protocol';
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
}
}
if (proxy['plugin-opts']?.tls) {
if (isPresent(proxy, 'skip-cert-verify')) {
proxy['plugin-opts']['skip-cert-verify'] =
@@ -152,11 +170,6 @@ export default function Clash_Producer() {
}
delete proxy['tls-fingerprint'];
if (proxy['underlying-proxy']) {
proxy['dialer-proxy'] = proxy['underlying-proxy'];
}
delete proxy['underlying-proxy'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}

View File

@@ -1,12 +1,20 @@
import { isPresent } from '@/core/proxy-utils/producers/utils';
const ipVersions = {
dual: 'dual',
'v4-only': 'ipv4',
'v6-only': 'ipv6',
'prefer-v4': 'ipv4-prefer',
'prefer-v6': 'ipv6-prefer',
};
export default function ClashMeta_Producer() {
const type = 'ALL';
const produce = (proxies, type, opts = {}) => {
const list = proxies
.filter((proxy) => {
if (opts['include-unsupported-proxy']) return true;
if (proxy.type === 'snell' && String(proxy.version) === '4') {
if (proxy.type === 'snell' && proxy.version >= 4) {
return false;
} else if (['juicity'].includes(proxy.type)) {
return false;
@@ -190,6 +198,18 @@ export default function ClashMeta_Producer() {
proxy['h2-opts'].headers.host = [host];
}
}
if (proxy.network === 'ws') {
const wsPath = proxy['ws-opts']?.path;
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
// eslint-disable-next-line no-unused-vars
const [_, path = '', ed = ''] = reg.exec(wsPath);
proxy['ws-opts'].path = path;
if (ed !== '') {
proxy['ws-opts']['early-data-header-name'] =
'Sec-WebSocket-Protocol';
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
}
}
if (proxy['plugin-opts']?.tls) {
if (isPresent(proxy, 'skip-cert-verify')) {
@@ -242,6 +262,11 @@ export default function ClashMeta_Producer() {
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
}
if (proxy['ip-version']) {
proxy['ip-version'] =
ipVersions[proxy['ip-version']] || proxy['ip-version'];
}
return proxy;
});

View File

@@ -371,12 +371,37 @@ export default function Egern_Producer() {
};
}
}
if (
['ss'].includes(original.type) &&
proxy.shadow_tls &&
original['udp-port'] > 0 &&
original['udp-port'] <= 65535
) {
proxy['udp_port'] = original['udp-port'];
}
delete proxy.subName;
delete proxy.collectionName;
delete proxy.id;
delete proxy.resolved;
delete proxy['no-resolve'];
if (proxy.transport) {
for (const key in proxy.transport) {
if (
Object.keys(proxy.transport[key]).length === 0 ||
Object.values(proxy.transport[key]).every(
(v) => v == null,
)
) {
delete proxy.transport[key];
}
}
if (Object.keys(proxy.transport).length === 0) {
delete proxy.transport;
}
}
if (type !== 'internal') {
for (const key in proxy) {
if (proxy[key] == null || /^_/i.test(key)) {

View File

@@ -21,9 +21,9 @@ export default function Loon_Producer() {
case 'trojan':
return trojan(proxy);
case 'vmess':
return vmess(proxy);
return vmess(proxy, opts['include-unsupported-proxy']);
case 'vless':
return vless(proxy);
return vless(proxy, opts['include-unsupported-proxy']);
case 'http':
return http(proxy);
case 'socks5':
@@ -133,6 +133,13 @@ function shadowsocks(proxy) {
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -144,7 +151,7 @@ function shadowsocks(proxy) {
return result.toString();
}
function shadowsocksr(proxy, includeUnsupportedProxy) {
function shadowsocksr(proxy) {
const result = new Result(proxy);
result.append(
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
@@ -203,6 +210,13 @@ function shadowsocksr(proxy, includeUnsupportedProxy) {
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -259,6 +273,13 @@ function trojan(proxy) {
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -270,6 +291,8 @@ function trojan(proxy) {
}
function vmess(proxy) {
const isReality = !!proxy['reality-opts'];
const result = new Result(proxy);
result.append(
`${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.uuid}"`,
@@ -317,20 +340,32 @@ function vmess(proxy) {
'skip-cert-verify',
);
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
if (isReality) {
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,public-key="${proxy['reality-opts']['public-key']}"`,
'reality-opts.public-key',
);
result.appendIfPresent(
`,short-id=${proxy['reality-opts']['short-id']}`,
'reality-opts.short-id',
);
} else {
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
}
// AEAD
if (isPresent(proxy, 'aead')) {
result.append(`,alterId=0`);
result.append(`,alterId=${proxy.aead ? 0 : 1}`);
} else {
result.append(`,alterId=${proxy.alterId}`);
}
@@ -338,6 +373,13 @@ function vmess(proxy) {
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -348,9 +390,17 @@ function vmess(proxy) {
}
function vless(proxy) {
if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
throw new Error(`VLESS XTLS/REALITY is not supported`);
let isXtls = false;
const isReality = !!proxy['reality-opts'];
if (typeof proxy.flow !== 'undefined') {
if (['xtls-rprx-vision'].includes(proxy.flow)) {
isXtls = true;
} else {
throw new Error(`VLESS flow(${proxy.flow}) is not supported`);
}
}
const result = new Result(proxy);
result.append(
`${proxy.name}=vless,${proxy.server},${proxy.port},"${proxy.uuid}"`,
@@ -398,20 +448,42 @@ function vless(proxy) {
'skip-cert-verify',
);
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
if (isXtls) {
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
}
if (isReality) {
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,public-key="${proxy['reality-opts']['public-key']}"`,
'reality-opts.public-key',
);
result.appendIfPresent(
`,short-id=${proxy['reality-opts']['short-id']}`,
'reality-opts.short-id',
);
} else {
// sni
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,tls-cert-sha256=${proxy['tls-fingerprint']}`,
'tls-fingerprint',
);
result.appendIfPresent(
`,tls-pubkey-sha256=${proxy['tls-pubkey-sha256']}`,
'tls-pubkey-sha256',
);
}
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -439,6 +511,14 @@ function http(proxy) {
// tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
@@ -465,6 +545,13 @@ function socks5(proxy) {
// tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);
@@ -539,6 +626,13 @@ function wireguard(proxy) {
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
return result.toString();
}
@@ -573,6 +667,13 @@ function hysteria2(proxy) {
// tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
// block-quic
if (proxy['block-quic'] === 'on') {
result.append(',block-quic=true');
} else if (proxy['block-quic'] === 'off') {
result.append(',block-quic=false');
}
// udp
if (proxy.udp) {
result.append(`,udp=true`);

View File

@@ -58,6 +58,8 @@ function shadowsocks(proxy) {
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
@@ -128,6 +130,20 @@ function shadowsocks(proxy) {
// udp
appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
// udp over tcp
if (proxy['_ssr_python_uot']) {
append(`,udp-over-tcp=true`);
} else if (proxy['udp-over-tcp']) {
if (
!proxy['udp-over-tcp-version'] ||
proxy['udp-over-tcp-version'] === 1
) {
append(`,udp-over-tcp=sp.v1`);
} else if (proxy['udp-over-tcp-version'] === 2) {
append(`,udp-over-tcp=sp.v2`);
}
}
// server_check_url
result.appendIfPresent(
`,server_check_url=${proxy['test-url']}`,
@@ -389,6 +405,8 @@ function vless(proxy) {
else append(`,obfs=ws`);
} else if (proxy.network === 'http') {
append(`,obfs=http`);
} else if (['tcp'].includes(proxy.network)) {
if (proxy.tls) append(`,obfs=over-tls`);
} else if (!['tcp'].includes(proxy.network)) {
throw new Error(`network ${proxy.network} is unsupported`);
}

View File

@@ -1,4 +1,5 @@
import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Shadowrocket_Producer() {
const type = 'ALL';
@@ -6,9 +7,9 @@ export default function Shadowrocket_Producer() {
const list = proxies
.filter((proxy) => {
if (opts['include-unsupported-proxy']) return true;
if (proxy.type === 'snell' && String(proxy.version) === '4') {
if (proxy.type === 'snell' && proxy.version >= 4) {
return false;
} else if (['mieru', 'anytls'].includes(proxy.type)) {
} else if (['mieru'].includes(proxy.type)) {
return false;
}
return true;
@@ -165,6 +166,18 @@ export default function Shadowrocket_Producer() {
proxy['h2-opts'].headers.host = [host];
}
}
if (proxy.network === 'ws') {
const wsPath = proxy['ws-opts']?.path;
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
// eslint-disable-next-line no-unused-vars
const [_, path = '', ed = ''] = reg.exec(wsPath);
proxy['ws-opts'].path = path;
if (ed !== '') {
proxy['ws-opts']['early-data-header-name'] =
'Sec-WebSocket-Protocol';
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
}
}
if (proxy['plugin-opts']?.tls) {
if (isPresent(proxy, 'skip-cert-verify')) {
proxy['plugin-opts']['skip-cert-verify'] =

View File

@@ -2,6 +2,26 @@ import ClashMeta_Producer from './clashmeta';
import $ from '@/core/app';
import { isIPv4, isIPv6 } from '@/utils';
const ipVersions = {
ipv4: 'ipv4_only',
ipv6: 'ipv6_only',
'v4-only': 'ipv4_only',
'v6-only': 'ipv6_only',
'ipv4-prefer': 'prefer_ipv4',
'ipv6-prefer': 'prefer_ipv6',
'prefer-v4': 'prefer_ipv4',
'prefer-v6': 'prefer_ipv6',
};
const ipVersionParser = (proxy, parsedProxy) => {
const strategy = ipVersions[proxy['ip-version']];
if (proxy._dns_server && strategy) {
parsedProxy.domain_resolver = {
server: proxy._dns_server,
strategy,
};
}
};
const detourParser = (proxy, parsedProxy) => {
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
};
@@ -31,12 +51,34 @@ const smuxParser = (smux, proxy) => {
if (smux['min-streams'])
proxy.multiplex.min_streams = parseInt(`${smux['min-streams']}`, 10);
if (smux.padding) proxy.multiplex.padding = true;
if (smux['brutal-opts']?.up || smux['brutal-opts']?.down) {
proxy.multiplex.brutal = {
enabled: true,
};
if (smux['brutal-opts']?.up)
proxy.multiplex.brutal.up_mbps = parseInt(
`${smux['brutal-opts']?.up}`,
10,
);
if (smux['brutal-opts']?.down)
proxy.multiplex.brutal.down_mbps = parseInt(
`${smux['brutal-opts']?.down}`,
10,
);
}
};
const wsParser = (proxy, parsedProxy) => {
const transport = { type: 'ws', headers: {} };
if (proxy['ws-opts']) {
const { path: wsPath = '', headers: wsHeaders = {} } = proxy['ws-opts'];
const {
path: wsPath = '',
headers: wsHeaders = {},
'max-early-data': max_early_data,
'early-data-header-name': early_data_header_name,
} = proxy['ws-opts'];
transport.early_data_header_name = early_data_header_name;
transport.max_early_data = parseInt(max_early_data, 10);
if (wsPath !== '') transport.path = `${wsPath}`;
if (Object.keys(wsHeaders).length > 0) {
const headers = {};
@@ -139,7 +181,7 @@ const h1Parser = (proxy, parsedProxy) => {
host = `${host}`.split(',').map((i) => i.trim());
if (host.length > 0) transport.host = host;
}
if (!transport.host) return;
// if (!transport.host) return;
if (proxy['http-path'] && proxy['http-path'] !== '') {
const path = proxy['http-path'];
if (Array.isArray(path)) {
@@ -148,7 +190,7 @@ const h1Parser = (proxy, parsedProxy) => {
}
if (parsedProxy.tls.insecure)
parsedProxy.tls.server_name = transport.host[0];
if (transport.host.length === 1) transport.host = transport.host[0];
if (transport.host?.length === 1) transport.host = transport.host[0];
for (const key of Object.keys(transport.headers)) {
const value = transport.headers[key];
if (value.length === 1) transport.headers[key] = value[0];
@@ -263,6 +305,7 @@ const sshParser = (proxy = {}) => {
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -290,6 +333,7 @@ const httpParser = (proxy = {}) => {
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -312,6 +356,7 @@ const socks5Parser = (proxy = {}) => {
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -323,6 +368,17 @@ const shadowTLSParser = (proxy = {}) => {
password: proxy.password,
detour: `${proxy.name}_shadowtls`,
};
if (proxy.uot) ssPart.udp_over_tcp = true;
if (proxy['udp-over-tcp']) {
ssPart.udp_over_tcp = {
enabled: true,
version:
!proxy['udp-over-tcp-version'] ||
proxy['udp-over-tcp-version'] === 1
? 1
: 2,
};
}
const stPart = {
tag: `${proxy.name}_shadowtls`,
type: 'shadowtls',
@@ -345,6 +401,7 @@ const shadowTLSParser = (proxy = {}) => {
tfoParser(proxy, stPart);
detourParser(proxy, stPart);
smuxParser(proxy.smux, ssPart);
ipVersionParser(proxy, stPart);
return { type: 'ss-with-st', ssPart, stPart };
};
const ssParser = (proxy = {}) => {
@@ -359,12 +416,22 @@ const ssParser = (proxy = {}) => {
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.uot) parsedProxy.udp_over_tcp = true;
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
if (proxy['udp-over-tcp']) {
parsedProxy.udp_over_tcp = {
enabled: true,
version:
!proxy['udp-over-tcp-version'] ||
proxy['udp-over-tcp-version'] === 1
? 1
: 2,
};
}
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
if (proxy.plugin) {
const optArr = [];
if (proxy.plugin === 'obfs') {
@@ -443,6 +510,7 @@ const ssrParser = (proxy = {}) => {
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -481,6 +549,7 @@ const vmessParser = (proxy = {}) => {
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -495,15 +564,20 @@ const vlessParser = (proxy = {}) => {
};
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
throw 'invalid port';
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
// if (['xtls-rprx-vision', ''].includes(proxy.flow)) parsedProxy.flow = proxy.flow;
if (proxy.flow != null) parsedProxy.flow = proxy.flow;
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
tlsParser(proxy, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
const trojanParser = (proxy = {}) => {
@@ -525,6 +599,7 @@ const trojanParser = (proxy = {}) => {
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
const hysteriaParser = (proxy = {}) => {
@@ -543,12 +618,13 @@ const hysteriaParser = (proxy = {}) => {
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
// eslint-disable-next-line no-control-regex
const reg = new RegExp('^[0-9]+[ \t]*[KMGT]*[Bb]ps$');
if (reg.test(`${proxy.up}`)) {
// sing-box 跟文档不一致, 但是懒得全转, 只处理最常见的 Mbps
if (reg.test(`${proxy.up}`) && !`${proxy.up}`.endsWith('Mbps')) {
parsedProxy.up = `${proxy.up}`;
} else {
parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
}
if (reg.test(`${proxy.down}`)) {
if (reg.test(`${proxy.down}`) && !`${proxy.down}`.endsWith('Mbps')) {
parsedProxy.down = `${proxy.down}`;
} else {
parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
@@ -573,9 +649,10 @@ const hysteriaParser = (proxy = {}) => {
detourParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
const hysteria2Parser = (proxy = {}) => {
const parsedProxy = {
tag: proxy.name,
type: 'hysteria2',
@@ -606,6 +683,7 @@ const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
const tuic5Parser = (proxy = {}) => {
@@ -637,6 +715,7 @@ const tuic5Parser = (proxy = {}) => {
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
const anytlsParser = (proxy = {}) => {
@@ -652,8 +731,14 @@ const anytlsParser = (proxy = {}) => {
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`;
if (/^\d+$/.test(proxy['min-idle-session']))
parsedProxy.min_idle_session = parseInt(
`${proxy['min-idle-session']}`,
10,
);
detourParser(proxy, parsedProxy);
tlsParser(proxy, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};
@@ -711,6 +796,7 @@ const wireguardParser = (proxy = {}) => {
tfoParser(proxy, parsedProxy);
detourParser(proxy, parsedProxy);
smuxParser(proxy.smux, parsedProxy);
ipVersionParser(proxy, parsedProxy);
return parsedProxy;
};

View File

@@ -1,4 +1,5 @@
import { isPresent } from '@/core/proxy-utils/producers/utils';
import $ from '@/core/app';
export default function Stash_Producer() {
const type = 'ALL';
@@ -39,21 +40,20 @@ export default function Stash_Producer() {
'xchacha20',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
...(opts['include-unsupported-proxy']
? [
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
].includes(proxy.cipher)) ||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
(opts['include-unsupported-proxy']
? proxy.type === 'vless' &&
proxy['reality-opts'] &&
!['xtls-rprx-vision'].includes(proxy.flow)
: proxy.type === 'vless' && proxy['reality-opts'])
(proxy.type === 'snell' && proxy.version >= 4) ||
(proxy.type === 'vless' &&
proxy['reality-opts'] &&
!['xtls-rprx-vision'].includes(proxy.flow))
) {
return false;
} else if (proxy['underlying-proxy'] || proxy['dialer-proxy']) {
$.error(
`Stash 暂不支持前置代理字段. 已过滤节点 ${proxy.name}. 请使用 代理的转发链 https://stash.wiki/proxy-protocols/proxy-groups#relay`,
);
return false;
}
return true;
})
@@ -238,6 +238,18 @@ export default function Stash_Producer() {
proxy['h2-opts'].headers.host = [host];
}
}
if (proxy.network === 'ws') {
const wsPath = proxy['ws-opts']?.path;
const reg = /^(.*?)(?:\?ed=(\d+))?$/;
// eslint-disable-next-line no-unused-vars
const [_, path = '', ed = ''] = reg.exec(wsPath);
proxy['ws-opts'].path = path;
if (ed !== '') {
proxy['ws-opts']['early-data-header-name'] =
'Sec-WebSocket-Protocol';
proxy['ws-opts']['max-early-data'] = parseInt(ed, 10);
}
}
if (proxy['plugin-opts']?.tls) {
if (isPresent(proxy, 'skip-cert-verify')) {
proxy['plugin-opts']['skip-cert-verify'] =
@@ -261,11 +273,6 @@ export default function Stash_Producer() {
}
delete proxy['tls-fingerprint'];
if (proxy['underlying-proxy']) {
proxy['dialer-proxy'] = proxy['underlying-proxy'];
}
delete proxy['underlying-proxy'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}

View File

@@ -370,9 +370,9 @@ function vmess(proxy, includeUnsupportedProxy) {
function ssh(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
result.appendIfPresent(`,${proxy.username}`, 'username');
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
// 所有的类似的字段都有双引号的问题 暂不处理
result.appendIfPresent(`,"${proxy.password}"`, 'password');
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
// https://manual.nssurge.com/policy/ssh.html
// 需配合 Keystore
@@ -439,8 +439,8 @@ function http(proxy) {
const result = new Result(proxy);
const type = proxy.tls ? 'https' : 'http';
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,${proxy.username}`, 'username');
result.appendIfPresent(`,"${proxy.password}"`, 'password');
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
@@ -565,8 +565,8 @@ function socks5(proxy) {
const result = new Result(proxy);
const type = proxy.tls ? 'socks5-tls' : 'socks5';
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,${proxy.username}`, 'username');
result.appendIfPresent(`,"${proxy.password}"`, 'password');
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');

View File

@@ -12,7 +12,7 @@ export default function URI_Producer() {
delete proxy.resolved;
delete proxy['no-resolve'];
for (const key in proxy) {
if (proxy[key] == null || /^_/i.test(key)) {
if (proxy[key] == null) {
delete proxy[key];
}
}
@@ -23,13 +23,19 @@ 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) {
case 'socks5':
result = `socks://${encodeURIComponent(
Base64.encode(`${proxy.username}:${proxy.password}`),
Base64.encode(
`${proxy.username ?? ''}:${proxy.password ?? ''}`,
),
)}@${proxy.server}:${proxy.port}#${proxy.name}`;
break;
case 'ss':
@@ -113,12 +119,17 @@ export default function URI_Producer() {
v: '2',
ps: proxy.name,
add: proxy.server,
port: proxy.port,
port: `${proxy.port}`,
id: proxy.uuid,
type,
aid: proxy.alterId || 0,
aid: `${proxy.alterId || 0}`,
scy: proxy.cipher,
net,
type,
tls: proxy.tls ? 'tls' : '',
alpn: Array.isArray(proxy.alpn)
? proxy.alpn.join(',')
: proxy.alpn,
fp: proxy['client-fingerprint'],
};
if (proxy.tls && proxy.sni) {
result.sni = proxy.sni;
@@ -129,16 +140,7 @@ export default function URI_Producer() {
proxy[`${proxy.network}-opts`]?.path;
let vmessTransportHost =
proxy[`${proxy.network}-opts`]?.headers?.Host;
if (vmessTransportPath) {
result.path = Array.isArray(vmessTransportPath)
? vmessTransportPath[0]
: vmessTransportPath;
}
if (vmessTransportHost) {
result.host = Array.isArray(vmessTransportHost)
? vmessTransportHost[0]
: vmessTransportHost;
}
if (['grpc'].includes(proxy.network)) {
result.path =
proxy[`${proxy.network}-opts`]?.[
@@ -150,6 +152,31 @@ export default function URI_Producer() {
'gun';
result.host =
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
} else if (['kcp', 'quic'].includes(proxy.network)) {
// https://github.com/XTLS/Xray-core/issues/91
result.type =
proxy[`${proxy.network}-opts`]?.[
`_${proxy.network}-type`
] || 'none';
result.host =
proxy[`${proxy.network}-opts`]?.[
`_${proxy.network}-host`
];
result.path =
proxy[`${proxy.network}-opts`]?.[
`_${proxy.network}-path`
];
} else {
if (vmessTransportPath) {
result.path = Array.isArray(vmessTransportPath)
? vmessTransportPath[0]
: vmessTransportPath;
}
if (vmessTransportHost) {
result.host = Array.isArray(vmessTransportHost)
? vmessTransportHost[0]
: vmessTransportHost;
}
}
}
result = 'vmess://' + Base64.encode(JSON.stringify(result));
@@ -474,7 +501,7 @@ export default function URI_Producer() {
hysteriaParams.push(`obfsParam=${proxy[key]}`);
} else if (['sni'].includes(key)) {
hysteriaParams.push(`peer=${proxy[key]}`);
} else if (proxy[key]) {
} else if (proxy[key] && !/^_/i.test(key)) {
hysteriaParams.push(
`${i}=${encodeURIComponent(proxy[key])}`,
);
@@ -531,7 +558,13 @@ export default function URI_Producer() {
proxy[key]
) {
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
} else if (proxy[key]) {
} else if (
['congestion-controller'].includes(key)
) {
tuicParams.push(
`congestion_control=${proxy[key]}`,
);
} else if (proxy[key] && !/^_/i.test(key)) {
tuicParams.push(
`${i.replace(
/-/g,
@@ -579,7 +612,11 @@ export default function URI_Producer() {
if (proxy[key]) {
anytlsParams.push(`insecure=1`);
}
} else if (proxy[key]) {
} else if (['udp'].includes(key)) {
if (proxy[key]) {
anytlsParams.push(`udp=1`);
}
} else if (proxy[key] && !/^_/i.test(key)) {
anytlsParams.push(
`${i.replace(/-/g, '_')}=${encodeURIComponent(
proxy[key],
@@ -616,7 +653,7 @@ export default function URI_Producer() {
if (proxy[key]) {
wireguardParams.push(`${key}=1`);
}
} else if (proxy[key]) {
} else if (proxy[key] && !/^_/i.test(key)) {
wireguardParams.push(
`${key}=${encodeURIComponent(proxy[key])}`,
);

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,6 @@ console.log(
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
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

@@ -14,10 +14,12 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
`
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
Sub-Store -- v${version}
Loon -- ${$loon}
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
`,
);
const build = $loon.match(/\((\d+)\)$/)?.[1];
let arg;
if (typeof $argument != 'undefined') {
arg = Object.fromEntries(
@@ -26,23 +28,28 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
} else {
arg = {};
}
console.log(`arg: ${JSON.stringify(arg)}`);
const RESOURCE_TYPE = {
PROXY: 1,
RULE: 2,
};
result = resource;
if (!arg.resourceUrlOnly) {
result = resource;
}
if (resourceType === RESOURCE_TYPE.PROXY) {
try {
let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
});
} catch (e) {
console.log('解析器: 使用 resource 出现错误');
console.log(e.message ?? e);
if (!arg.resourceUrlOnly) {
try {
let proxies = ProxyUtils.parse(resource);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 842,
});
} catch (e) {
console.log('解析器: 使用 resource 出现错误');
console.log(e.message ?? e);
}
}
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
@@ -59,18 +66,21 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
);
let proxies = ProxyUtils.parse(raw);
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
'include-unsupported-proxy':
arg?.includeUnsupportedProxy || build >= 842,
});
} catch (e) {
console.log(e.message ?? e);
}
}
} else if (resourceType === RESOURCE_TYPE.RULE) {
try {
const rules = RuleUtils.parse(resource);
result = RuleUtils.produce(rules, 'Loon');
} catch (e) {
console.log(e.message ?? e);
if (!arg.resourceUrlOnly) {
try {
const rules = RuleUtils.parse(resource);
result = RuleUtils.produce(rules, 'Loon');
} catch (e) {
console.log(e.message ?? e);
}
}
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);

View File

@@ -112,7 +112,6 @@ function replaceArtifact(req, res) {
async function getArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name);
@@ -163,7 +162,6 @@ function createArtifact(req, res) {
function updateArtifact(req, res) {
const allArtifacts = $.read(ARTIFACTS_KEY);
let oldName = req.params.name;
oldName = decodeURIComponent(oldName);
const artifact = findByName(allArtifacts, oldName);
if (artifact) {
$.info(`正在更新远程配置:${artifact.name}`);
@@ -197,7 +195,6 @@ function updateArtifact(req, res) {
async function deleteArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除远程配置:${name}`);
const allArtifacts = $.read(ARTIFACTS_KEY);
try {

View File

@@ -1,8 +1,9 @@
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
import { COLLECTIONS_KEY, ARTIFACTS_KEY, FILES_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);
@@ -51,7 +52,6 @@ function createCollection(req, res) {
function getCollection(req, res) {
let { name } = req.params;
let { raw } = req.query;
name = decodeURIComponent(name);
const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name);
if (collection) {
@@ -60,16 +60,9 @@ function getCollection(req, res) {
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(
`sub-store_collection_${name}_${new Date()
.toLocaleString('zh-CN', {
year: 'numeric',
day: 'numeric',
month: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})
.replace(/\D/g, '')}.json`,
`sub-store_collection_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(collection));
@@ -90,7 +83,6 @@ function getCollection(req, res) {
function updateCollection(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
let collection = req.body;
const allCols = $.read(COLLECTIONS_KEY);
const oldCol = findByName(allCols, name);
@@ -112,7 +104,18 @@ function updateCollection(req, res) {
artifact.source = newCol.name;
}
}
// update all files referring this collection
const allFiles = $.read(FILES_KEY) || [];
for (const file of allFiles) {
if (
file.sourceType === 'collection' &&
file.sourceName === oldCol.name
) {
file.sourceName = newCol.name;
}
}
$.write(allArtifacts, ARTIFACTS_KEY);
$.write(allFiles, FILES_KEY);
}
updateByName(allCols, name, newCol);
@@ -132,7 +135,6 @@ function updateCollection(req, res) {
function deleteCollection(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除组合订阅:${name}`);
let allCols = $.read(COLLECTIONS_KEY);
deleteByName(allCols, name);

View File

@@ -88,8 +88,6 @@ export default function register($app) {
async function downloadSubscription(req, res) {
let { name, nezhaIndex } = req.params;
name = decodeURIComponent(name);
nezhaIndex = decodeURIComponent(nezhaIndex);
const useMihomoExternal = req.query.target === 'SurgeMac';
@@ -111,7 +109,17 @@ async function downloadSubscription(req, res) {
proxy,
noCache,
} = req.query;
let $options = {};
let $options = {
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) {
try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
@@ -130,7 +138,6 @@ async function downloadSubscription(req, res) {
$.info(`传入 $options: ${JSON.stringify($options)}`);
}
if (url) {
url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`);
if (!/^https?:\/\//.test(url)) {
content = url;
@@ -138,32 +145,25 @@ async function downloadSubscription(req, res) {
}
}
if (content) {
content = decodeURIComponent(content);
$.info(`指定本地订阅: ${content}`);
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程订阅 User-Agent: ${ua}`);
}
if (mergeSources) {
mergeSources = decodeURIComponent(mergeSources);
$.info(`指定合并来源: ${mergeSources}`);
}
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
}
if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`);
}
if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
@@ -249,7 +249,7 @@ async function downloadSubscription(req, res) {
}
}
}
if (!$arguments.noFlow) {
if (!$arguments.noFlow && /^https?/.test(url)) {
// forward flow headers
flowInfo = await getFlowHeaders(
$arguments?.insecure ? `${url}#insecure` : url,
@@ -352,8 +352,6 @@ async function downloadSubscription(req, res) {
async function downloadCollection(req, res) {
let { name, nezhaIndex } = req.params;
name = decodeURIComponent(name);
nezhaIndex = decodeURIComponent(nezhaIndex);
const useMihomoExternal = req.query.target === 'SurgeMac';
@@ -376,7 +374,17 @@ async function downloadCollection(req, res) {
noCache,
} = req.query;
let $options = {};
let $options = {
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) {
try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
@@ -396,21 +404,17 @@ async function downloadCollection(req, res) {
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
}
if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`);
}
if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
@@ -486,7 +490,7 @@ async function downloadCollection(req, res) {
}
}
}
if (!$arguments.noFlow) {
if (!$arguments.noFlow && /^https?:/.test(url)) {
subUserInfoOfSub = await getFlowHeaders(
$arguments?.insecure ? `${url}#insecure` : url,
$arguments.flowUserAgent,

View File

@@ -1,6 +1,6 @@
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { getFlowHeaders, normalizeFlowHeader } from '@/utils/flow';
import { FILES_KEY } from '@/constants';
import { FILES_KEY, ARTIFACTS_KEY } from '@/constants';
import { failed, success } from '@/restful/response';
import $ from '@/core/app';
import {
@@ -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);
@@ -50,9 +51,8 @@ function createFile(req, res) {
async function getFile(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在下载文件:${name}`);
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`);
let {
url,
subInfoUrl,
@@ -63,8 +63,19 @@ async function getFile(req, res) {
ignoreFailedRemoteFile,
proxy,
noCache,
produceType,
} = req.query;
let $options = {};
let $options = {
_req: {
method: req.method,
url: req.url,
path: req.path,
query: req.query,
params: req.params,
headers: req.headers,
body: req.body,
},
};
if (req.query.$options) {
try {
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
@@ -83,40 +94,35 @@ async function getFile(req, res) {
$.info(`传入 $options: ${JSON.stringify($options)}`);
}
if (url) {
url = decodeURIComponent(url);
$.info(`指定远程文件 URL: ${url}`);
}
if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
}
if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程文件 User-Agent: ${ua}`);
}
if (subInfoUrl) {
subInfoUrl = decodeURIComponent(subInfoUrl);
$.info(`指定获取流量的 subInfoUrl: ${subInfoUrl}`);
}
if (subInfoUserAgent) {
subInfoUserAgent = decodeURIComponent(subInfoUserAgent);
$.info(`指定获取流量的 subInfoUserAgent: ${subInfoUserAgent}`);
}
if (content) {
content = decodeURIComponent(content);
$.info(`指定本地文件: ${content}`);
}
if (mergeSources) {
mergeSources = decodeURIComponent(mergeSources);
$.info(`指定合并来源: ${mergeSources}`);
}
if (ignoreFailedRemoteFile != null && ignoreFailedRemoteFile !== '') {
ignoreFailedRemoteFile = decodeURIComponent(ignoreFailedRemoteFile);
$.info(`指定忽略失败的远程文件: ${ignoreFailedRemoteFile}`);
}
if (noCache) {
$.info(`指定不使用缓存: ${noCache}`);
}
if (produceType) {
$.info(`指定生产类型: ${produceType}`);
}
const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name);
@@ -133,6 +139,8 @@ async function getFile(req, res) {
$options,
proxy,
noCache,
produceType,
all: true,
});
try {
@@ -167,9 +175,15 @@ async function getFile(req, res) {
)}`,
);
}
res.set('Content-Type', 'text/plain; charset=utf-8').send(
output ?? '',
);
res.set('Content-Type', 'text/plain; charset=utf-8');
if (output?.$options?._res?.headers) {
Object.entries(output.$options._res.headers).forEach(
([key, value]) => {
res.set(key, value);
},
);
}
res.send(output?.$content ?? '');
} catch (err) {
$.notify(
`🌍 Sub-Store 下载文件失败`,
@@ -201,7 +215,6 @@ async function getFile(req, res) {
function getWholeFile(req, res) {
let { name } = req.params;
let { raw } = req.query;
name = decodeURIComponent(name);
const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name);
if (file) {
@@ -210,16 +223,9 @@ function getWholeFile(req, res) {
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(
`sub-store_file_${name}_${new Date()
.toLocaleString('zh-CN', {
year: 'numeric',
day: 'numeric',
month: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})
.replace(/\D/g, '')}.json`,
`sub-store_file_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(file));
@@ -240,7 +246,6 @@ function getWholeFile(req, res) {
function updateFile(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
let file = req.body;
const allFiles = $.read(FILES_KEY);
const oldFile = findByName(allFiles, name);
@@ -251,6 +256,20 @@ function updateFile(req, res) {
};
$.info(`正在更新文件:${name}...`);
if (name !== newFile.name) {
// update all artifacts referring this collection
const allArtifacts = $.read(ARTIFACTS_KEY) || [];
for (const artifact of allArtifacts) {
if (
artifact.type === 'file' &&
artifact.source === oldFile.name
) {
artifact.source = newFile.name;
}
}
$.write(allArtifacts, ARTIFACTS_KEY);
}
updateByName(allFiles, name, newFile);
$.write(allFiles, FILES_KEY);
success(res, newFile);
@@ -268,7 +287,6 @@ function updateFile(req, res) {
function deleteFile(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除文件:${name}`);
let allFiles = $.read(FILES_KEY);
deleteByName(allFiles, name);

View File

@@ -1,7 +1,9 @@
import { Base64 } from 'js-base64';
import _ from 'lodash';
import express from '@/vendor/express';
import $ from '@/core/app';
import migrate from '@/utils/migration';
import download from '@/utils/download';
import download, { downloadFile } from '@/utils/download';
import { syncArtifacts, produceArtifact } from '@/restful/sync';
import { gistBackupAction } from '@/restful/miscs';
import { TOKENS_KEY } from '@/constants';
@@ -29,6 +31,77 @@ export default function serve() {
host = eval('process.env.SUB_STORE_BACKEND_API_HOST') || '::';
}
const $app = express({ substore: $, port, host });
if ($.env.isNode) {
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
if (be_prefix || be_merge) {
if (!fe_be_path.startsWith('/')) {
throw new Error(
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
);
}
if (be_merge) {
$.info(`[BACKEND] MERGE mode is [ON].`);
$.info(`[BACKEND && FRONTEND] ${host}:${port}`);
}
$.info(`[BACKEND PREFIX] ${host}:${port}${fe_be_path}`);
$app.use((req, res, next) => {
if (req.path.startsWith(fe_be_path)) {
req.url = req.url.replace(fe_be_path, '') || '/';
if (be_merge && req.url.startsWith('/api/')) {
req.query['share'] = 'true';
}
next();
return;
}
const pathname =
decodeURIComponent(req._parsedUrl.pathname) || '/';
if (
be_merge &&
req.path.startsWith('/share/') &&
req.query.token
) {
if (req.method.toLowerCase() !== 'get') {
res.status(405).send('Method not allowed');
return;
}
const tokens = $.read(TOKENS_KEY) || [];
const token = tokens.find(
(t) =>
t.token === req.query.token &&
`/share/${t.type}/${t.name}` === pathname &&
(t.exp == null || t.exp > Date.now()),
);
if (token) {
next();
return;
}
}
if (be_merge && fe_path && req.path.indexOf('/', 1) == -1) {
if (req.path.indexOf('.') == -1) {
req.url = '/index.html';
}
const express_ = eval(`require("express")`);
const mime_ = eval(`require("mime-types")`);
const path_ = eval(`require("path")`);
const staticFileMiddleware = express_.static(fe_path, {
setHeaders: (res, path) => {
const type = mime_.contentType(path_.extname(path));
if (type) {
res.set('Content-Type', type);
}
},
});
staticFileMiddleware(req, res, next);
return;
}
res.status(404).end();
return;
});
}
}
// register routes
registerCollectionRoutes($app);
registerSubscriptionRoutes($app);
@@ -48,10 +121,11 @@ export default function serve() {
$app.start();
if ($.env.isNode) {
// Deprecated: SUB_STORE_BACKEND_CRON
const backend_sync_cron =
eval('process.env.SUB_STORE_BACKEND_SYNC_CRON') ||
eval('process.env.SUB_STORE_BACKEND_CRON');
// Deprecated: SUB_STORE_BACKEND_CRON, SUB_STORE_CRON
const backend_sync_cron = eval(
'process.env.SUB_STORE_BACKEND_SYNC_CRON',
);
if (backend_sync_cron) {
$.info(`[SYNC CRON] ${backend_sync_cron} enabled`);
const { CronJob } = eval(`require("cron")`);
@@ -74,6 +148,17 @@ export default function serve() {
true, // start
// 'Asia/Shanghai' // timeZone
);
} else {
if (eval('process.env.SUB_STORE_BACKEND_CRON')) {
$.error(
`[SYNC CRON] SUB_STORE_BACKEND_CRON 已弃用, 请使用 SUB_STORE_BACKEND_SYNC_CRON`,
);
}
if (eval('process.env.SUB_STORE_CRON')) {
$.error(
`[SYNC CRON] SUB_STORE_CRON 已弃用, 请使用 SUB_STORE_BACKEND_SYNC_CRON`,
);
}
}
// 格式: 0 */2 * * *,sub,a;0 */3 * * *,col,b
// 每 2 小时处理一次单条订阅 a, 每 3 小时处理一次组合订阅 b
@@ -164,9 +249,64 @@ export default function serve() {
// 'Asia/Shanghai' // timeZone
);
}
const mmdb_cron = eval('process.env.SUB_STORE_MMDB_CRON');
const countryFile = eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH');
const countryUrl = eval('process.env.SUB_STORE_MMDB_COUNTRY_URL');
const asnFile = eval('process.env.SUB_STORE_MMDB_ASN_PATH');
const asnUrl = eval('process.env.SUB_STORE_MMDB_ASN_URL');
if (mmdb_cron && ((countryFile && countryUrl) || (asnFile && asnUrl))) {
$.info(`[MMDB CRON] ${mmdb_cron} enabled`);
const { CronJob } = eval(`require("cron")`);
new CronJob(
mmdb_cron,
async function () {
try {
$.info(`[MMDB CRON] ${mmdb_cron} started`);
if (countryFile && countryUrl) {
try {
$.info(
`[MMDB CRON] downloading ${countryUrl} to ${countryFile}`,
);
await downloadFile(countryUrl, countryFile);
} catch (e) {
$.error(
`[MMDB CRON] ${countryUrl} download failed: ${
e.message ?? e
}`,
);
}
}
if (asnFile && asnUrl) {
try {
$.info(
`[MMDB CRON] downloading ${asnUrl} to ${asnFile}`,
);
await downloadFile(asnUrl, asnFile);
} catch (e) {
$.error(
`[MMDB CRON] ${asnUrl} download failed: ${
e.message ?? e
}`,
);
}
}
$.info(`[MMDB CRON] ${mmdb_cron} finished`);
} catch (e) {
$.error(
`[MMDB CRON] ${mmdb_cron} error: ${e.message ?? e}`,
);
}
}, // onTick
null, // onComplete
true, // start
// 'Asia/Shanghai' // timeZone
);
}
const path = eval(`require("path")`);
const fs = eval(`require("fs")`);
const data_url = eval('process.env.SUB_STORE_DATA_URL');
const data_url_post = eval('process.env.SUB_STORE_DATA_URL_POST');
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
const fe_port = eval('process.env.SUB_STORE_FRONTEND_PORT') || 3001;
const fe_host =
@@ -175,7 +315,8 @@ export default function serve() {
const fe_abs_path = path.resolve(
fe_path || path.join(__dirname, 'frontend'),
);
if (fe_path) {
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
if (fe_path && !be_merge) {
try {
fs.accessSync(path.join(fe_abs_path, 'index.html'));
} catch (e) {
@@ -194,11 +335,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(
@@ -215,9 +360,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) || [];
@@ -229,34 +374,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;
},
}),
);
}
@@ -276,10 +413,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}`,
@@ -290,10 +427,39 @@ export default function serve() {
if (data_url) {
$.info(`[BACKEND] downloading data from ${data_url}`);
download(data_url)
.then((content) => {
$.write(content, '#sub-store');
.then(async (content) => {
try {
content = JSON.parse(Base64.decode(content));
if (Object.keys(content.settings).length === 0) {
throw new Error(
'备份文件应该至少包含 settings 字段',
);
}
} catch (err) {
try {
content = JSON.parse(content);
if (Object.keys(content.settings).length === 0) {
throw new Error(
'备份文件应该至少包含 settings 字段',
);
}
} catch (err) {
$.error(
`Gist 备份文件校验失败, 无法还原\nReason: ${
err.message ?? err
}`,
);
throw new Error('Gist 备份文件校验失败, 无法还原');
}
}
if (data_url_post) {
$.info('[BACKEND] executing post-processing script');
eval(data_url_post);
}
$.cache = JSON.parse(content);
$.write(JSON.stringify(content, null, ` `), '#sub-store');
$.cache = content;
$.persistCache();
migrate();

View File

@@ -1,3 +1,5 @@
import { Base64 } from 'js-base64';
import _ from 'lodash';
import $ from '@/core/app';
import { ENV } from '@/vendor/open-api';
import { failed, success } from '@/restful/response';
@@ -14,6 +16,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
@@ -28,16 +31,7 @@ export default function register($app) {
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(
`sub-store_data_${new Date()
.toLocaleString('zh-CN', {
year: 'numeric',
day: 'numeric',
month: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})
.replace(/\D/g, '')}.json`,
`sub-store_data_${formatDateTime(new Date())}.json`,
)}"`,
)
.send(
@@ -47,21 +41,47 @@ export default function register($app) {
);
})
.post((req, res) => {
const { content } = req.body;
$.write(content, '#sub-store');
let { content } = req.body;
try {
content = JSON.parse(Base64.decode(content));
if (Object.keys(content.settings).length === 0) {
throw new Error('备份文件应该至少包含 settings 字段');
}
} catch (err) {
try {
content = JSON.parse(content);
if (Object.keys(content.settings).length === 0) {
throw new Error('备份文件应该至少包含 settings 字段');
}
} catch (err) {
$.error(
`备份文件校验失败, 无法还原\nReason: ${
err.message ?? err
}`,
);
throw new Error('备份文件校验失败, 无法还原');
}
}
$.write(JSON.stringify(content, null, ` `), '#sub-store');
if ($.env.isNode) {
$.cache = JSON.parse(content);
$.cache = content;
$.persistCache();
}
migrate();
success(res);
});
// Redirect sub.store to vercel webpage
$app.get('/', async (req, res) => {
// 302 redirect
res.set('location', 'https://sub-store.vercel.app/').status(302).end();
});
if (ENV().isNode) {
$app.get('/', getEnv);
} else {
// Redirect sub.store to vercel webpage
$app.get('/', async (req, res) => {
// 302 redirect
res.set('location', 'https://sub-store.vercel.app/')
.status(302)
.end();
});
}
// handle preflight request for QX
if (ENV().isQX) {
@@ -79,7 +99,19 @@ function getEnv(req, res) {
if (req.query.share) {
env.feature.share = true;
}
success(res, env);
res.set('Content-Type', 'application/json;charset=UTF-8').send(
JSON.stringify(
{
status: 'success',
data: {
guide: '⚠️⚠️⚠️ 您当前看到的是后端的响应. 若想配合前端使用, 可访问官方前端 https://sub-store.vercel.app 后自行配置后端地址, 或一键配置后端 https://sub-store.vercel.app?api=https://a.com/xxx (假设 https://a.com 是你后端的域名, /xxx 是自定义路径). 需注意 HTTPS 前端无法请求非本地的 HTTP 后端(部分浏览器上也无法访问本地 HTTP 后端). 请配置反代或在局域网自建 HTTP 前端. 如果还有问题, 可查看此排查说明: https://t.me/zhetengsha/1068',
...env,
},
},
null,
2,
),
);
}
async function refresh(_, res) {
@@ -94,7 +126,7 @@ async function refresh(_, res) {
success(res);
}
async function gistBackupAction(action) {
async function gistBackupAction(action, keep, encode) {
// read token
const { gistToken, syncPlatform } = $.read(SETTINGS_KEY);
if (!gistToken) throw new Error('GitHub Token is required for backup!');
@@ -104,6 +136,9 @@ async function gistBackupAction(action) {
key: GIST_BACKUP_KEY,
syncPlatform,
});
let currentContent = $.read('#sub-store');
currentContent = currentContent ? JSON.parse(currentContent) : {};
if ($.env.isNode) currentContent = JSON.parse(JSON.stringify($.cache));
let content;
const settings = $.read(SETTINGS_KEY);
const updated = settings.syncTime;
@@ -111,7 +146,18 @@ async function gistBackupAction(action) {
case 'upload':
try {
content = $.read('#sub-store');
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
content = content ? JSON.parse(content) : {};
if ($.env.isNode) content = JSON.parse(JSON.stringify($.cache));
if (encode === 'plaintext') {
content.settings.gistToken =
'恢复后请重新设置 GitHub Token';
content = JSON.stringify(content, null, ` `);
} else {
content = Base64.encode(
JSON.stringify(content, null, ` `),
);
}
$.info(`下载备份, 与本地内容对比...`);
const onlineContent = await gist.download(
GIST_BACKUP_FILE_NAME,
@@ -128,7 +174,14 @@ async function gistBackupAction(action) {
settings.syncTime = new Date().getTime();
$.write(settings, SETTINGS_KEY);
content = $.read('#sub-store');
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
content = content ? JSON.parse(content) : {};
if ($.env.isNode) content = JSON.parse(JSON.stringify($.cache));
if (encode === 'plaintext') {
content.settings.gistToken = '恢复后请重新设置 GitHub Token';
content = JSON.stringify(content, null, ` `);
} else {
content = Base64.encode(JSON.stringify(content, null, ` `));
}
$.info(`上传备份中...`);
try {
await gist.upload({
@@ -146,21 +199,34 @@ async function gistBackupAction(action) {
$.info(`还原备份中...`);
content = await gist.download(GIST_BACKUP_FILE_NAME);
try {
if (Object.keys(JSON.parse(content).settings).length === 0) {
content = JSON.parse(Base64.decode(content));
if (Object.keys(content.settings).length === 0) {
throw new Error('备份文件应该至少包含 settings 字段');
}
} catch (err) {
$.error(
`Gist 备份文件校验失败, 无法还原\nReason: ${
err.message ?? err
}`,
);
throw new Error('Gist 备份文件校验失败, 无法还原');
try {
content = JSON.parse(content);
if (Object.keys(content.settings).length === 0) {
throw new Error('备份文件应该至少包含 settings 字段');
}
} catch (err) {
$.error(
`Gist 备份文件校验失败, 无法还原\nReason: ${
err.message ?? err
}`,
);
throw new Error('Gist 备份文件校验失败, 无法还原');
}
}
if (keep) {
$.info(`保留原有设置 ${keep}`);
keep.split(',').forEach((path) => {
_.set(content, path, _.get(currentContent, path));
});
}
// restore settings
$.write(content, '#sub-store');
$.write(JSON.stringify(content, null, ` `), '#sub-store');
if ($.env.isNode) {
content = JSON.parse(content);
$.cache = content;
$.persistCache();
}
@@ -172,7 +238,7 @@ async function gistBackupAction(action) {
}
}
async function gistBackup(req, res) {
const { action } = req.query;
const { action, keep, encode } = req.query;
// read token
const { gistToken } = $.read(SETTINGS_KEY);
if (!gistToken) {
@@ -185,7 +251,7 @@ async function gistBackup(req, res) {
);
} else {
try {
await gistBackupAction(action);
await gistBackupAction(action, keep, encode);
success(res);
} catch (err) {
$.error(

View File

@@ -43,7 +43,6 @@ function createModule(req, res) {
function getModule(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
const allModules = $.read(MODULES_KEY);
const module = findByName(allModules, name);
if (module) {
@@ -64,7 +63,6 @@ function getModule(req, res) {
function updateModule(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
let module = req.body;
const allModules = $.read(MODULES_KEY);
const oldModule = findByName(allModules, name);
@@ -92,7 +90,6 @@ function updateModule(req, res) {
function deleteModule(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除模块:${name}`);
let allModules = $.read(MODULES_KEY);
deleteByName(allModules, name);

View File

@@ -31,7 +31,12 @@ async function previewFile(req, res) {
.filter((i) => i.length)
.map(async (url) => {
try {
return await download(url, file.ua);
return await download(
url,
file.ua,
undefined,
file.proxy,
);
} catch (err) {
errors[url] = err;
$.error(
@@ -42,15 +47,22 @@ async function previewFile(req, res) {
}),
);
if (
!file.ignoreFailedRemoteFile &&
Object.keys(errors).length > 0
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!file.ignoreFailedRemoteFile) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (file.ignoreFailedRemoteFile === 'enabled') {
$.notify(
`🌍 Sub-Store 预览文件失败`,
`${file.name}`,
`远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (file.mergeSources === 'localFirst') {
content.unshift(file.content);
@@ -131,12 +143,22 @@ async function compareSub(req, res) {
}),
);
if (!sub.ignoreFailedRemoteSub && Object.keys(errors).length > 0) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!sub.ignoreFailedRemoteSub) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
} else if (sub.ignoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 预览订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (sub.mergeSources === 'localFirst') {
content.unshift(sub.content);
@@ -239,15 +261,25 @@ async function compareCollection(req, res) {
}
}),
);
if (
!sub.ignoreFailedRemoteSub &&
Object.keys(errors).length > 0
) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!sub.ignoreFailedRemoteSub) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (
sub.ignoreFailedRemoteSub === 'enabled'
) {
$.notify(
`🌍 Sub-Store 预览订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content);
@@ -279,20 +311,28 @@ async function compareCollection(req, res) {
errors[name] = err;
$.error(
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}`,
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name} 时出现错误:${err}`,
);
}
}),
);
if (
!collection.ignoreFailedRemoteSub &&
Object.keys(errors).length > 0
) {
throw new Error(
`组合订阅 ${collection.name} 中的子订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!collection.ignoreFailedRemoteSub) {
throw new Error(
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (collection.ignoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 预览组合订阅失败`,
`${collection.name}`,
`子订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
// merge proxies with the original order
const original = Array.prototype.concat.apply(

View File

@@ -12,7 +12,9 @@ export function failed(resp, error, statusCode) {
code: error.code,
type: error.type,
message: error.message,
details: error.details,
details: resp.req?.route?.path?.startsWith('/share/')
? '详情请查看日志'
: error.details,
},
});
}

View File

@@ -61,7 +61,7 @@ async function updateSettings(req, res) {
export async function updateAvatar() {
const settings = $.read(SETTINGS_KEY);
const { githubUser: username, syncPlatform } = settings;
const { githubUser: username, syncPlatform, githubProxy } = settings;
if (username) {
if (syncPlatform === 'gitlab') {
try {
@@ -92,7 +92,9 @@ export async function updateAvatar() {
try {
const data = await $.http
.get({
url: `https://api.github.com/users/${encodeURIComponent(
url: `${
githubProxy ? `${githubProxy}/` : ''
}https://api.github.com/users/${encodeURIComponent(
username,
)}`,
headers: {
@@ -134,11 +136,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

@@ -5,7 +5,12 @@ import {
RequestInvalidError,
} from './errors';
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
import {
SUBS_KEY,
COLLECTIONS_KEY,
ARTIFACTS_KEY,
FILES_KEY,
} from '@/constants';
import {
getFlowHeaders,
parseFlowHeaders,
@@ -13,6 +18,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);
@@ -33,10 +39,8 @@ export default function register($app) {
// subscriptions API
async function getFlowInfo(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
let { url } = req.query;
if (url) {
url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`);
}
const allSubs = $.read(SUBS_KEY);
@@ -134,7 +138,7 @@ async function getFlowInfo(req, res) {
}
}
}
if ($arguments.noFlow) {
if ($arguments.noFlow || !/^https?/.test(url)) {
failed(
res,
new RequestInvalidError(
@@ -226,6 +230,7 @@ async function getFlowInfo(req, res) {
function createSubscription(req, res) {
const sub = req.body;
delete sub.subscriptions;
$.info(`正在创建订阅: ${sub.name}`);
if (/\//.test(sub.name)) {
failed(
@@ -256,25 +261,18 @@ function createSubscription(req, res) {
function getSubscription(req, res) {
let { name } = req.params;
let { raw } = req.query;
name = decodeURIComponent(name);
const allSubs = $.read(SUBS_KEY);
const sub = findByName(allSubs, name);
delete sub.subscriptions;
if (sub) {
if (raw) {
res.set('content-type', 'application/json')
.set(
'content-disposition',
`attachment; filename="${encodeURIComponent(
`sub-store_subscription_${name}_${new Date()
.toLocaleString('zh-CN', {
year: 'numeric',
day: 'numeric',
month: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})
.replace(/\D/g, '')}.json`,
`sub-store_subscription_${name}_${formatDateTime(
new Date(),
)}.json`,
)}"`,
)
.send(JSON.stringify(sub));
@@ -295,8 +293,8 @@ function getSubscription(req, res) {
function updateSubscription(req, res) {
let { name } = req.params;
name = decodeURIComponent(name); // the original name
let sub = req.body;
delete sub.subscriptions;
const allSubs = $.read(SUBS_KEY);
const oldSub = findByName(allSubs, name);
if (oldSub) {
@@ -326,9 +324,20 @@ function updateSubscription(req, res) {
artifact.source = sub.name;
}
}
// update all files referring this subscription
const allFiles = $.read(FILES_KEY) || [];
for (const file of allFiles) {
if (
file.sourceType === 'subscription' &&
file.sourceName == name
) {
file.sourceName = sub.name;
}
}
$.write(allCols, COLLECTIONS_KEY);
$.write(allArtifacts, ARTIFACTS_KEY);
$.write(allFiles, FILES_KEY);
}
updateByName(allSubs, name, newSub);
$.write(allSubs, SUBS_KEY);
@@ -347,7 +356,6 @@ function updateSubscription(req, res) {
function deleteSubscription(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`删除订阅:${name}...`);
// delete from subscriptions
let allSubs = $.read(SUBS_KEY);

View File

@@ -40,6 +40,7 @@ async function produceArtifact({
$options,
proxy,
noCache,
all,
}) {
platform = platform || 'JSON';
@@ -89,12 +90,23 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
}
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!subIgnoreFailedRemoteSub) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
} else if (subIgnoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (mergeSources === 'localFirst') {
raw.unshift(content);
@@ -138,12 +150,23 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
subIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
}
if (!subIgnoreFailedRemoteSub && Object.keys(errors).length > 0) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!subIgnoreFailedRemoteSub) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
} else if (subIgnoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content);
@@ -151,6 +174,9 @@ async function produceArtifact({
raw.push(sub.content);
}
}
if (produceType === 'raw') {
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
}
// parse proxies
let proxies = (Array.isArray(raw) ? raw : [raw])
.map((i) => ProxyUtils.parse(i))
@@ -264,15 +290,25 @@ async function produceArtifact({
}
}),
);
if (
!sub.ignoreFailedRemoteSub &&
Object.keys(errors).length > 0
) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!sub.ignoreFailedRemoteSub) {
throw new Error(
`订阅 ${sub.name} 的远程订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (
sub.ignoreFailedRemoteSub === 'enabled'
) {
$.notify(
`🌍 Sub-Store 处理订阅失败`,
`${sub.name}`,
`远程订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (sub.mergeSources === 'localFirst') {
raw.unshift(sub.content);
@@ -327,15 +363,23 @@ async function produceArtifact({
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
collectionIgnoreFailedRemoteSub = ignoreFailedRemoteSub;
}
if (
!collectionIgnoreFailedRemoteSub &&
Object.keys(errors).length > 0
) {
throw new Error(
`组合订阅 ${name} 中的子订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!collectionIgnoreFailedRemoteSub) {
throw new Error(
`组合订阅 ${collection.name} 的子订阅 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (collectionIgnoreFailedRemoteSub === 'enabled') {
$.notify(
`🌍 Sub-Store 处理组合订阅失败`,
`${collection.name}`,
`子订阅 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
// merge proxies with the original order
@@ -411,7 +455,6 @@ async function produceArtifact({
const file = findByName(allFiles, name);
if (!file) throw new Error(`找不到文件 ${name}`);
let raw = '';
console.log(file);
if (file.type !== 'mihomoProfile') {
if (
content &&
@@ -506,15 +549,23 @@ async function produceArtifact({
) {
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
}
if (
!fileIgnoreFailedRemoteFile &&
Object.keys(errors).length > 0
) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
if (Object.keys(errors).length > 0) {
if (!fileIgnoreFailedRemoteFile) {
throw new Error(
`文件 ${file.name} 的远程文件 ${Object.keys(
errors,
).join(', ')} 发生错误, 请查看日志`,
);
} else if (fileIgnoreFailedRemoteFile === 'enabled') {
$.notify(
`🌍 Sub-Store 处理文件失败`,
`${file.name}`,
`远程文件 ${Object.keys(errors).join(
', ',
)} 发生错误, 请查看日志`,
);
}
}
if (file.mergeSources === 'localFirst') {
raw.unshift(file.content);
@@ -523,6 +574,9 @@ async function produceArtifact({
}
}
}
if (produceType === 'raw') {
return JSON.stringify((Array.isArray(raw) ? raw : [raw]).flat());
}
const files = (Array.isArray(raw) ? raw : [raw]).flat();
let filesContent = files
.filter((i) => i != null && i !== '')
@@ -542,7 +596,7 @@ async function produceArtifact({
)
: { $content: filesContent, $files: files, $options };
return processed?.$content ?? '';
return (all ? processed : processed?.$content) ?? '';
}
}
@@ -557,8 +611,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);
@@ -579,6 +635,13 @@ async function syncArtifacts() {
}
});
if (enabledCount === 0) {
$.info(
`需同步的配置: ${enabledCount}, 总数: ${allArtifacts.length}`,
);
return;
}
if (subNames.length > 0) {
await Promise.all(
subNames.map(async (subName) => {
@@ -729,7 +792,6 @@ async function syncAllArtifacts(_, res) {
async function syncArtifact(req, res) {
let { name } = req.params;
name = decodeURIComponent(name);
$.info(`开始同步远程配置 ${name}...`);
const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name);

View File

@@ -17,7 +17,6 @@ export default function register($app) {
function deleteToken(req, res) {
let { token } = req.params;
token = decodeURIComponent(token);
$.info(`正在删除:${token}`);
let allTokens = $.read(TOKENS_KEY);
deleteByName(allTokens, token, 'token');
@@ -53,6 +52,16 @@ async function signToken(req, res) {
try {
const { payload, options } = req.body;
const ms = eval(`require("ms")`);
const type = payload?.type;
const name = payload?.name;
if (!type || !name)
return failed(
res,
new RequestInvalidError(
'INVALID_PAYLOAD',
`payload type and name are required`,
),
);
let token = payload?.token;
if (token != null) {
if (typeof token !== 'string' || token.length < 1) {
@@ -65,7 +74,12 @@ async function signToken(req, res) {
);
}
const tokens = $.read(TOKENS_KEY) || [];
if (tokens.find((t) => t.token === token)) {
if (
tokens.find(
(t) =>
t.token === token && t.type === type && t.name === name,
)
) {
return failed(
res,
new RequestInvalidError(
@@ -75,16 +89,7 @@ async function signToken(req, res) {
);
}
}
const type = payload?.type;
const name = payload?.name;
if (!type || !name)
return failed(
res,
new RequestInvalidError(
'INVALID_PAYLOAD',
`payload type and name are required`,
),
);
if (type === 'col') {
const collections = $.read(COLLECTIONS_KEY) || [];
const collection = collections.find((c) => c.name === name);
@@ -153,7 +158,12 @@ async function signToken(req, res) {
if (!token) {
do {
token = nanoid.customAlphabet(nanoid.urlAlphabet)();
} while (tokens.find((t) => t.token === token));
} while (
tokens.find(
(t) =>
t.token === token && t.type === type && t.name === name,
)
);
}
tokens.push({
...payload,

View File

@@ -1,4 +1,4 @@
import { SETTINGS_KEY } from '@/constants';
import { SETTINGS_KEY, FILES_KEY, MODULES_KEY } from '@/constants';
import { HTTP, ENV } from '@/vendor/open-api';
import { hex_md5 } from '@/vendor/md5';
import { getPolicyDescriptor } from '@/utils';
@@ -11,7 +11,11 @@ import {
validCheck,
} from '@/utils/flow';
import $ from '@/core/app';
import { findByName } from '@/utils/database';
import { produceArtifact } from '@/restful/sync';
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
import { ProxyUtils } from '@/core/proxy-utils';
const clashPreprocessor = PROXY_PREPROCESSORS.find(
(processor) => processor.name === 'Clash Pre-processor',
);
@@ -130,22 +134,53 @@ export default async function download(
}
}
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
// if (downloadUrlMatch) {
// let type = downloadUrlMatch?.[1];
// let name = downloadUrlMatch?.[2];
// if (name == null) {
// throw new Error(`本地 ${type} URL 无效: ${url}`);
// }
// name = decodeURIComponent(name);
// const key = type === 'module' ? MODULES_KEY : FILES_KEY;
// const item = findByName($.read(key), name);
// if (!item) {
// throw new Error(`找不到本地 ${type}: ${name}`);
// }
const downloadUrlMatch = url
.split('#')[0]
.match(/^\/api\/(file|module)\/(.+)/);
if (downloadUrlMatch) {
let type = '';
try {
type = downloadUrlMatch?.[1];
let name = downloadUrlMatch?.[2];
if (name == null) {
throw new Error(`本地 ${type} URL 无效: ${url}`);
}
name = decodeURIComponent(name);
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
const item = findByName($.read(key), name);
if (!item) {
throw new Error(`找不到 ${type}: ${name}`);
}
// return item.content;
// }
if (type === 'module') {
return item.content;
} else {
return await produceArtifact({
type: 'file',
name,
});
}
} catch (err) {
$.error(
`Error when loading ${type}: ${
url.split('#')[0]
}.\n Reason: ${err}`,
);
throw new Error(`无法加载 ${type}: ${url}`);
}
} else if (url?.startsWith('/')) {
try {
const fs = eval(`require("fs")`);
return fs.readFileSync(url.split('#')[0], 'utf8');
} catch (err) {
$.error(
`Error when reading local file: ${
url.split('#')[0]
}.\n Reason: ${err}`,
);
throw new Error(`无法从该路径读取文本内容: ${url}`);
}
}
if (!isNode && tasks.has(id)) {
return tasks.get(id);
@@ -225,6 +260,20 @@ export default async function download(
shouldCache = false;
}
}
if (preprocess) {
try {
const proxies = ProxyUtils.parse(body);
if (!Array.isArray(proxies) || proxies.length === 0) {
$.error(`URL ${url} 不包含有效节点, 不缓存`);
shouldCache = false;
}
} catch (e) {
$.error(
`URL ${url} 尝试解析节点失败 ${e.message ?? e}, 不缓存`,
);
shouldCache = false;
}
}
if (shouldCache) {
resourceCache.set(id, body);
if (customCacheKey) {
@@ -273,3 +322,25 @@ export default async function download(
}
return result;
}
export async function downloadFile(url, file) {
const undici = eval("require('undici')");
const fs = eval("require('fs')");
const { pipeline } = eval("require('stream/promises')");
const { Agent, interceptors, request } = undici;
$.info(`Downloading file...\nURL: ${url}\nFile: ${file}`);
const { body, statusCode } = await request(url, {
dispatcher: new Agent().compose(
interceptors.redirect({
maxRedirections: 3,
throwOnRedirect: true,
}),
),
});
if (statusCode !== 200)
throw new Error(`Failed to download file from ${url}`);
const fileStream = fs.createWriteStream(file);
await pipeline(body, fileStream);
$.info(`File downloaded from ${url} to ${file}`);
return file;
}

View File

@@ -49,7 +49,7 @@ export async function getFlowHeaders(
}
}
}
if ($arguments?.noFlow) {
if ($arguments?.noFlow || !/^https?/.test(url)) {
return;
}
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
@@ -230,7 +230,7 @@ export function flowTransfer(flow, unit = 'B') {
let unitIndex = unitList.indexOf(unit);
return flow < 1024 || unitIndex === unitList.length - 1
? { value: flow.toFixed(1), unit: unit }
? { value: (Math.round(flow * 100) / 100).toString(), unit: unit }
: flowTransfer(flow / 1024, unitList[++unitIndex]);
}
@@ -334,7 +334,28 @@ export function normalizeFlowHeader(flowHeaders) {
if (!kvMap.has(key)) {
try {
// 解码 URI 组件并保留原始值作为 fallback
const decodedValue = decodeURIComponent(encodedValue);
let decodedValue = decodeURIComponent(encodedValue);
if (
['upload', 'download', 'total', 'expire'].includes(
key,
)
) {
try {
decodedValue = Number(decodedValue).toFixed(0);
if (
['expire'].includes(key) &&
decodedValue <= 0
) {
decodedValue = '';
}
} catch (e) {
$.error(
`Failed to convert value for key "${key}=${encodedValue}": ${
e.message ?? e
}`,
);
}
}
kvMap.set(key, decodedValue);
} catch (e) {
kvMap.set(key, encodedValue);

View File

@@ -46,6 +46,7 @@ const ISOFlags = {
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
'🇭🇷': ['HR', 'HRV'],
'🇭🇺': ['HU', 'HUN'],
'🇮🇶': ['IQ', 'IRQ'], // 伊拉克
'🇯🇴': ['JO', 'JOR'],
'🇯🇵': ['JP', 'JPN', 'TYO'],
'🇰🇪': ['KE', 'KEN'],
@@ -99,6 +100,7 @@ const ISOFlags = {
'🇸🇬': ['SG', 'SGP'],
'🇸🇮': ['SI', 'SVN'],
'🇸🇰': ['SK', 'SVK'],
'🇹🇬': ['TG', 'TGO'], // 多哥
'🇹🇭': ['TH', 'THA'],
'🇹🇳': ['TN', 'TUN'],
'🇹🇷': ['TR', 'TUR'],
@@ -220,6 +222,7 @@ export function getFlag(name) {
],
'🇭🇷': ['Croatia', '克罗地亚', '克羅地亞'],
'🇭🇺': ['Hungary', '匈牙利'],
'🇮🇶': ['Iraq', '伊拉克', '巴格达', 'Baghdad'], // 伊拉克
'🇯🇴': ['Jordan', '约旦'],
'🇯🇵': [
'Japan',
@@ -338,6 +341,7 @@ export function getFlag(name) {
],
'🇸🇮': ['Slovenia', '斯洛文尼亚'],
'🇸🇰': ['Slovakia', '斯洛伐克'],
'🇹🇬': ['Togo', '多哥', '洛美', 'Lomé', 'Lome'], // 多哥
'🇹🇭': ['Thailand', '泰国', '泰國', '曼谷'],
'🇹🇳': ['Tunisia', '突尼斯'],
'🇹🇷': ['Turkey', '土耳其', '伊斯坦布尔', 'Istanbul'],

View File

@@ -9,7 +9,11 @@ import { SETTINGS_KEY } from '@/constants';
export default class Gist {
constructor({ token, key, syncPlatform }) {
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
const { defaultProxy, defaultTimeout: timeout } = $.read(SETTINGS_KEY);
const {
defaultProxy,
defaultTimeout: timeout,
githubProxy,
} = $.read(SETTINGS_KEY);
let proxy = defaultProxy;
if ($.env.isNode) {
proxy =
@@ -63,7 +67,9 @@ export default class Gist {
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36',
};
this.http = HTTP({
baseURL: 'https://api.github.com',
baseURL: `${
githubProxy ? `${githubProxy}/` : ''
}https://api.github.com`,
headers: {
...this.headers,
...(isStash && proxy
@@ -280,7 +286,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

@@ -4,6 +4,8 @@ import {
SCHEMA_VERSION_KEY,
ARTIFACTS_KEY,
RULES_KEY,
FILES_KEY,
TOKENS_KEY,
} from '@/constants';
import $ from '@/core/app';
@@ -55,7 +57,17 @@ function doMigrationV2() {
const newRules = Object.values(rules);
$.write(newRules, RULES_KEY);
// 5. delete builtin rules
// 5. migrate files
const files = $.read(FILES_KEY) || {};
const newFiles = Object.values(files);
$.write(newFiles, FILES_KEY);
// 6. migrate tokens
const tokens = $.read(TOKENS_KEY) || {};
const newTokens = Object.values(tokens);
$.write(newTokens, TOKENS_KEY);
// 7. delete builtin rules
delete $.cache.builtin;
$.info('Migration complete!');

View File

@@ -47,7 +47,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) {
return 'Clash';
} else if (ua.indexOf('v2ray') !== -1) {
return 'V2Ray';
} else if (ua.indexOf('sing-box') !== -1) {
} else if (ua.indexOf('sing-box') !== -1 || ua.indexOf('singbox') !== -1) {
return 'sing-box';
} else if (accept.indexOf('application/json') === 0) {
return 'JSON';
@@ -61,31 +61,41 @@ export function getPlatformFromHeaders(headers) {
return getPlatformFromUserAgent({ ua, UA, accept });
}
export function shouldIncludeUnsupportedProxy(platform, ua) {
try {
const target = getPlatformFromUserAgent({
UA: ua,
ua: ua.toLowerCase(),
});
if (!['Stash', 'Egern'].includes(target)) {
return false;
}
const version = coerce(ua).version;
if (
platform === 'Stash' &&
target === 'Stash' &&
gte(version, '2.8.0')
) {
return true;
}
if (
platform === 'Egern' &&
target === 'Egern' &&
gte(version, '1.29.0')
) {
return true;
}
} catch (e) {
$.error(`获取版本号失败: ${e}`);
}
// try {
// const target = getPlatformFromUserAgent({
// UA: ua,
// ua: ua.toLowerCase(),
// });
// if (!['Stash', 'Egern', 'Loon'].includes(target)) {
// return false;
// }
// const coerceVersion = coerce(ua);
// $.log(JSON.stringify(coerceVersion, null, 2));
// const { version } = coerceVersion;
// if (
// platform === 'Stash' &&
// target === 'Stash' &&
// gte(version, '3.1.0')
// ) {
// return true;
// }
// if (
// platform === 'Egern' &&
// target === 'Egern' &&
// gte(version, '1.29.0')
// ) {
// return true;
// }
// // Loon 的 UA 不规范, version 取出来是 build
// if (
// platform === 'Loon' &&
// target === 'Loon' &&
// gte(version, '842.0.0')
// ) {
// return true;
// }
// } catch (e) {
// $.error(`获取版本号失败: ${e}`);
// }
return false;
}

View File

@@ -34,4 +34,6 @@ export default {
load,
safeDump,
dump,
parse: safeLoad,
stringify: safeDump,
};

View File

@@ -17,10 +17,12 @@ export default function express({ substore: $, port, host }) {
const express_ = eval(`require("express")`);
const bodyParser = eval(`require("body-parser")`);
const app = express_();
const limit = eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb';
$.info(`[BACKEND] body JSON limit: ${limit}`);
app.use(
bodyParser.json({
verify: rawBodySaver,
limit: eval('process.env.SUB_STORE_BODY_JSON_LIMIT') || '1mb',
limit,
}),
);
app.use(
@@ -34,9 +36,12 @@ export default function express({ substore: $, port, host }) {
// adapter
app.start = () => {
app.get('*', function (req, res) {
res.status(404).end();
});
const listener = app.listen(port, host, () => {
const { address, port } = listener.address();
$.info(`[BACKEND] ${address}:${port}`);
$.info(`[BACKEND] listening on ${address}:${port}`);
});
};
return app;
@@ -265,7 +270,7 @@ function extractURL(url) {
let hashes = url.slice(url.indexOf('?') + 1).split('&');
for (let i = 0; i < hashes.length; i++) {
const hash = hashes[i].split('=');
query[hash[0]] = hash[1];
query[hash[0]] = decodeURIComponent(hash[1]);
}
}
return {
@@ -289,7 +294,7 @@ function extractPathParams(pattern, path) {
while (path[j] !== '/' && j < path.length) {
val.push(path[j++]);
}
params[key.join('')] = val.join('');
params[key.join('')] = decodeURIComponent(val.join(''));
} else {
if (pattern[i] !== path[j]) {
return null;

View File

@@ -9,7 +9,36 @@ const isShadowRocket = 'undefined' !== typeof $rocket;
const isEgern = 'object' == typeof egern;
const isLanceX = 'undefined' != typeof $native;
const isGUIforCores = typeof $Plugins !== 'undefined';
import { Base64 } from 'js-base64';
function isPlainObject(obj) {
return (
obj !== null &&
typeof obj === 'object' &&
[null, Object.prototype].includes(Object.getPrototypeOf(obj))
);
}
function parseSocks5Uri(uri) {
// eslint-disable-next-line no-unused-vars
let [__, username, password, server, port, query, name] = uri.match(
/^socks5:\/\/(?:(.*?):(.*?)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
);
if (port) {
port = parseInt(port, 10);
} else {
$.error(`port is not present in line: ${uri}`);
throw new Error(`port is not present in line: ${uri}`);
}
return {
type: 5,
host: server,
port,
userId: username != null ? decodeURIComponent(username) : undefined,
password: password != null ? decodeURIComponent(password) : undefined,
};
}
export class OpenAPI {
constructor(name = 'untitled', debug = false) {
this.name = name;
@@ -18,6 +47,10 @@ export class OpenAPI {
this.http = HTTP();
this.env = ENV();
if (isNode) {
const dotenv = eval(`require("dotenv")`);
dotenv.config();
}
this.node = (() => {
if (isNode) {
const fs = eval("require('fs')");
@@ -58,29 +91,64 @@ export class OpenAPI {
const basePath =
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
let rootPath = `${basePath}/root.json`;
const backupRootPath = `${basePath}/root_${Date.now()}.json`;
this.log(`Root path: ${rootPath}`);
if (!this.node.fs.existsSync(rootPath)) {
if (this.node.fs.existsSync(rootPath)) {
try {
this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
} catch (e) {
this.node.fs.copyFileSync(rootPath, backupRootPath);
this.error(
`Failed to parse ${rootPath}: ${e.message}. Backup created at ${backupRootPath}`,
);
}
}
if (!isPlainObject(this.root)) {
this.node.fs.writeFileSync(rootPath, JSON.stringify({}), {
flag: 'wx',
flag: 'w',
});
this.root = {};
} else {
this.root = JSON.parse(
this.node.fs.readFileSync(`${rootPath}`),
);
}
// create a json file with the given name if not exists
let fpath = `${basePath}/${this.name}.json`;
const backupPath = `${basePath}/${this.name}_${Date.now()}.json`;
this.log(`Data path: ${fpath}`);
if (!this.node.fs.existsSync(fpath)) {
if (this.node.fs.existsSync(fpath)) {
try {
this.cache = JSON.parse(
this.node.fs.readFileSync(`${fpath}`, 'utf-8'),
);
if (!isPlainObject(this.cache))
throw new Error('Invalid Data');
} catch (e) {
try {
const str = Base64.decode(
this.node.fs.readFileSync(`${fpath}`, 'utf-8'),
);
this.cache = JSON.parse(str);
this.node.fs.writeFileSync(fpath, str, {
flag: 'w',
});
if (!isPlainObject(this.cache))
throw new Error('Invalid Data');
} catch (e) {
this.node.fs.copyFileSync(fpath, backupPath);
this.error(
`Failed to parse ${fpath}: ${e.message}. Backup created at ${backupPath}`,
);
}
}
}
if (!isPlainObject(this.cache)) {
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
flag: 'wx',
flag: 'w',
});
this.cache = {};
} else {
this.cache = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
}
}
}
@@ -337,45 +405,130 @@ 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 { socksDispatcher } = eval("require('fetch-socks')");
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')}`,
};
}
let dispatcher;
if (!opts.proxy) {
const allProxy =
eval('process.env.all_proxy') ||
eval('process.env.ALL_PROXY');
if (allProxy && /^socks5:\/\//.test(allProxy)) {
opts.proxy = allProxy;
}
}
if (opts.proxy) {
if (/^socks5:\/\//.test(opts.proxy)) {
dispatcher = socksDispatcher(
parseSocks5Uri(opts.proxy),
agentOpts,
);
} else {
dispatcher = new ProxyAgent({
...agentOpts,
uri: opts.proxy,
});
}
} else {
dispatcher = new EnvHttpProxyAgent(agentOpts);
}
const response = await request(opts.url, {
...opts,
method: method.toUpperCase(),
dispatcher: dispatcher.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) => {
@@ -385,6 +538,7 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
url: options.url,
headers: options.headers,
body: options.body,
autoTransformBody: false,
options: {
Proxy: options.proxy,
Timeout: options.timeout

View File

@@ -1,40 +0,0 @@
upstream api {
server 0.0.0.0:3000;
}
server {
listen 6080;
# allow 127.0.0.1;
# allow 0.0.0.0;
# deny all;
gzip on;
gzip_static on;
gzip_types text/plain application/json application/javascript application/x-javascript text/css application/xml text/javascript;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.0;
location / {
root /Sub-Store/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api;
}
location /download {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api;
}
}

View File

@@ -13,15 +13,22 @@ function operator(proxies = [], targetPlatform, context) {
// 5. `_subName` 为单条订阅名, `_subDisplayName` 为单条订阅显示名
// 6. `_collectionName` 为组合订阅名, `_collectionDisplayName` 为组合订阅显示名
// 7. `tls-fingerprint` 为 tls 指纹
// 8. `underlying-proxy` 为前置代理
// 8. `underlying-proxy` 为前置代理, 不同平台会自动转换
// 例如 $server['underlying-proxy'] = '名称'
// 只给 mihomo 输出的话, `dialer-proxy` 也行
// 只给 sing-box 输出的话, `detour` 也行
// 只给 Egern 输出的话, `prev_hop` 也行
// 只给 Shadowrocket 输出的话, `chain` 也行
// 输出到 Clash/Stash 时, 会过滤掉配置了前置代理的节点, 并提示使用对应的功能.
// 9. `trojan`, `tuic`, `hysteria`, `hysteria2`, `juicity` 会在解析时设置 `tls`: true (会使用 tls 类协议的通用逻辑), 输出时删除
// 10. `sni` 在某些协议里会自动与 `servername` 转换
// 11. 读取节点的 ca-str 和 _ca (后端文件路径) 字段, 自动计算 fingerprint (参考 https://t.me/zhetengsha/1512)
// 12. 以 Surge 为例, 最新的参数一般我都会跟进, 以 Surge 文档为例, 一些常用的: TUIC/Hysteria 2 的 `ecn`, Snell 的 `reuse` 连接复用, QUIC 策略 block-quic`, Hysteria 2 下载带宽 `down`
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
// 15. `ip-version` 设置节点使用 IP 版本,可选dualipv4ipv6ipv4-preferipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
// 15. `ip-version` 设置节点使用 IP 版本,兼容各家的值. 会进行内部转换. sing-box 以外: 若无法匹配则使用原始值. sing-box: 需有匹配且节点上设置 `_dns_server` 字段, 将自动设置 `domain_resolver.server`
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
// 17. `block-quic` 支持 `auto`, `on`, `off`. 不同的平台不一定都支持, 会自动转换
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
@@ -40,8 +47,27 @@ function operator(proxies = [], targetPlatform, context) {
// 先这样处理 encodeURIComponent('arg1=a&arg2=b')
// /api/file/foo?$options=arg1%3Da%26arg2%3Db
// 默认会带上 _req 字段, 结构为
// {
// method,
// url,
// path,
// query,
// params,
// headers,
// body,
// }
// console.log($options)
// 若设置 $options._res.headers
// 则会在输出文件时设置响应头, 例如:
// $options._res = {
// headers: {
// 'X-Custom': '1'
// }
// }
// targetPlatform 为输出的目标平台
// lodash
@@ -79,6 +105,12 @@ function operator(proxies = [], targetPlatform, context) {
// 其他平台同理, 持久化缓存数据在 JSON 里
// 当配合脚本使用时, 可以在脚本的前面添加一个脚本操作, 实现保留 1 小时的缓存. 这样比较灵活
// async function operator() {
// scriptResourceCache._cleanup(undefined, 1 * 3600 * 1000);
// }
// ProxyUtils 为节点处理工具
// 可参考 https://t.me/zhetengsha/1066
// const ProxyUtils = {
@@ -96,9 +128,14 @@ function operator(proxies = [], targetPlatform, context) {
// getISO, // 获取 ISO 3166-1 alpha-2 代码
// Gist, // Gist 类
// download, // 内部的下载方法, 见 backend/src/utils/download.js
// downloadFile, // 下载二进制文件, 见 backend/src/utils/download.js
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
// isValidUUID, // 辅助判断是否为有效的 UUID
// Buffer, // https://github.com/feross/buffer
// Base64, // https://github.com/dankogai/js-base64
// JSON5, // https://github.com/json5/json5
// }
// 为兼容 https://github.com/xishang0128/sparkle 的 JavaScript 覆写, 也可以直接使用 `b64d`(Base64 解码), `b64e`(Base64 编码), `Buffer`, `yaml`(简单兼容了下 `yaml.parse` 和 `yaml.stringify`)
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
// ⚠️ 注意: 函数式(即本文件这样的 function operator() {}) 和快捷操作(下面使用 $server) 只能二选一
@@ -114,6 +151,18 @@ function operator(proxies = [], targetPlatform, context) {
// });
// $server.sni = sni
// 示例: 从 config 文件中读取配置项并进行节点操作
// config 的本地内容为
// {
// "reuse": false
// }
// 脚本操作为
// const config = (ProxyUtils.JSON5 || JSON).parse(await produceArtifact({
// type: 'file',
// name: 'config' // 文件名
// }))
// $server.reuse = config.reuse
// 1. Surge 输出 WireGuard 完整配置
// let proxies = await produceArtifact({
@@ -198,14 +247,14 @@ function operator(proxies = [], targetPlatform, context) {
// 这个历史遗留原因, 是有点复杂. 提供一个例子, 用来取当前脚本所在的组合订阅或单条订阅名称
// let name = ''
// for (const [key, value] of Object.entries(env.source)) {
// for (const [key, value] of Object.entries(context.source)) {
// if (!key.startsWith('_')) {
// name = value.displayName || value.name
// break
// }
// }
// if (!name) {
// const collection = env.source._collection
// const collection = context.source._collection
// name = collection.displayName || collection.name
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB