mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ec8a25a2 | ||
|
|
aa0943a909 | ||
|
|
a0c1bbbf70 | ||
|
|
fea9de4fae | ||
|
|
cddd1818fe | ||
|
|
f94830b2df | ||
|
|
e02a26040e | ||
|
|
6906efdd55 | ||
|
|
9558b63261 | ||
|
|
4bfdef17ee | ||
|
|
9d29fc8a09 | ||
|
|
f524920c13 | ||
|
|
bfe072cbdf | ||
|
|
32dcca4a26 | ||
|
|
a5d77c39c8 | ||
|
|
6ea1b69a62 | ||
|
|
2b3b9177e5 | ||
|
|
91aab3ca7a | ||
|
|
c1a9fc6abc | ||
|
|
11d9ce7372 | ||
|
|
ad3d2270ac | ||
|
|
3ad42f2c10 | ||
|
|
ec06eb8659 | ||
|
|
4a23a4d8b6 | ||
|
|
913638a233 | ||
|
|
bf642ce0e6 | ||
|
|
1ecac9da92 | ||
|
|
c5a417da8f | ||
|
|
8cd0545023 | ||
|
|
b6f848a6e6 | ||
|
|
99d058bcf1 | ||
|
|
533103e765 | ||
|
|
cf82764171 | ||
|
|
7b783c1fe3 | ||
|
|
372eff9a44 | ||
|
|
d3b5a529d7 | ||
|
|
8049134bb5 | ||
|
|
3f620700a4 | ||
|
|
9e64a68481 | ||
|
|
9ce5916414 | ||
|
|
047c21fe70 | ||
|
|
47849dc6d0 | ||
|
|
af06086c1b | ||
|
|
4a6a147667 | ||
|
|
c6540d14cd | ||
|
|
3db71ec531 | ||
|
|
cf156c2f17 | ||
|
|
e28e2a78fb | ||
|
|
b0a2c709e8 | ||
|
|
5dc2c8ced7 | ||
|
|
d2a65ee0fe | ||
|
|
4dd4ae98ca | ||
|
|
0d41eb467f | ||
|
|
ba1c91a7a5 | ||
|
|
30fa87c172 | ||
|
|
1eaa33948b | ||
|
|
619e256ed8 | ||
|
|
b46209e164 | ||
|
|
a1ba4e273e | ||
|
|
bfc95ed92a | ||
|
|
32f591ec56 | ||
|
|
cea16d8c44 | ||
|
|
93a1ba7b50 | ||
|
|
e6d1aa1150 | ||
|
|
26e83798da | ||
|
|
b083d2d840 | ||
|
|
cf35afcab2 | ||
|
|
d073dfeef8 | ||
|
|
f970ea3361 |
@@ -11,7 +11,7 @@ Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
||||||
|
<a href="https://trendshift.io/repositories/4572" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4572" alt="sub-store-org%2FSub-Store | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
[](https://www.buymeacoffee.com/PengYM)
|
[](https://www.buymeacoffee.com/PengYM)
|
||||||
|
|
||||||
Core functionalities:
|
Core functionalities:
|
||||||
@@ -35,7 +35,7 @@ Core functionalities:
|
|||||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||||
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||||
|
|
||||||
### Supported Target Platforms
|
### Supported Target Platforms
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.307",
|
"version": "2.14.352",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -17,13 +17,18 @@
|
|||||||
"author": "Peng-YM",
|
"author": "Peng-YM",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@maxmind/geoip2-node": "^5.0.0",
|
||||||
"automerge": "1.0.1-preview.7",
|
"automerge": "1.0.1-preview.7",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"connect-history-api-fallback": "^2.0.0",
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
"cron": "^3.1.6",
|
"cron": "^3.1.6",
|
||||||
|
"dns-packet": "^5.6.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-proxy-middleware": "^2.0.6",
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"ip-address": "^9.0.5",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
|
"jsrsasign": "^11.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"requests": "^0.3.0",
|
"requests": "^0.3.0",
|
||||||
|
|||||||
129
backend/pnpm-lock.yaml
generated
129
backend/pnpm-lock.yaml
generated
@@ -5,27 +5,42 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@maxmind/geoip2-node':
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: registry.npmmirror.com/@maxmind/geoip2-node@5.0.0
|
||||||
automerge:
|
automerge:
|
||||||
specifier: 1.0.1-preview.7
|
specifier: 1.0.1-preview.7
|
||||||
version: registry.npmmirror.com/automerge@1.0.1-preview.7
|
version: registry.npmmirror.com/automerge@1.0.1-preview.7
|
||||||
body-parser:
|
body-parser:
|
||||||
specifier: ^1.19.0
|
specifier: ^1.19.0
|
||||||
version: registry.npmmirror.com/body-parser@1.19.0
|
version: registry.npmmirror.com/body-parser@1.19.0
|
||||||
|
buffer:
|
||||||
|
specifier: ^6.0.3
|
||||||
|
version: registry.npmmirror.com/buffer@6.0.3
|
||||||
connect-history-api-fallback:
|
connect-history-api-fallback:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: registry.npmmirror.com/connect-history-api-fallback@2.0.0
|
version: registry.npmmirror.com/connect-history-api-fallback@2.0.0
|
||||||
cron:
|
cron:
|
||||||
specifier: ^3.1.6
|
specifier: ^3.1.6
|
||||||
version: registry.npmmirror.com/cron@3.1.6
|
version: registry.npmmirror.com/cron@3.1.6
|
||||||
|
dns-packet:
|
||||||
|
specifier: ^5.6.1
|
||||||
|
version: registry.npmmirror.com/dns-packet@5.6.1
|
||||||
express:
|
express:
|
||||||
specifier: ^4.17.1
|
specifier: ^4.17.1
|
||||||
version: registry.npmmirror.com/express@4.17.1
|
version: registry.npmmirror.com/express@4.17.1
|
||||||
http-proxy-middleware:
|
http-proxy-middleware:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: registry.npmmirror.com/http-proxy-middleware@2.0.6
|
version: registry.npmmirror.com/http-proxy-middleware@2.0.6
|
||||||
|
ip-address:
|
||||||
|
specifier: ^9.0.5
|
||||||
|
version: registry.npmmirror.com/ip-address@9.0.5
|
||||||
js-base64:
|
js-base64:
|
||||||
specifier: ^3.7.2
|
specifier: ^3.7.2
|
||||||
version: registry.npmmirror.com/js-base64@3.7.2
|
version: registry.npmmirror.com/js-base64@3.7.2
|
||||||
|
jsrsasign:
|
||||||
|
specifier: ^11.1.0
|
||||||
|
version: registry.npmmirror.com/jsrsasign@11.1.0
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: registry.npmmirror.com/lodash@4.17.21
|
version: registry.npmmirror.com/lodash@4.17.21
|
||||||
@@ -1967,6 +1982,21 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.13
|
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.13
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/@leichtgewicht/ip-codec@2.0.5:
|
||||||
|
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz}
|
||||||
|
name: '@leichtgewicht/ip-codec'
|
||||||
|
version: 2.0.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/@maxmind/geoip2-node@5.0.0:
|
||||||
|
resolution: {integrity: sha512-ki+q5//oU4tZ3BAhegZJcB5czoZyic5JSTEKbrUAQB/BzAoAiGyLW0immEmQvVVyy2SMlvBTJ3zqyRj8K9BbwQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@maxmind/geoip2-node/-/geoip2-node-5.0.0.tgz}
|
||||||
|
name: '@maxmind/geoip2-node'
|
||||||
|
version: 5.0.0
|
||||||
|
dependencies:
|
||||||
|
ip6addr: registry.npmmirror.com/ip6addr@0.2.5
|
||||||
|
maxmind: registry.npmmirror.com/maxmind@4.3.19
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/@sindresorhus/is@0.14.0:
|
registry.npmmirror.com/@sindresorhus/is@0.14.0:
|
||||||
resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz}
|
resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz}
|
||||||
name: '@sindresorhus/is'
|
name: '@sindresorhus/is'
|
||||||
@@ -2692,7 +2722,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz}
|
||||||
name: base64-js
|
name: base64-js
|
||||||
version: 1.5.1
|
version: 1.5.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/base@0.11.2:
|
registry.npmmirror.com/base@0.11.2:
|
||||||
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base/-/base-0.11.2.tgz}
|
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base/-/base-0.11.2.tgz}
|
||||||
@@ -3063,6 +3092,15 @@ packages:
|
|||||||
ieee754: registry.npmmirror.com/ieee754@1.2.1
|
ieee754: registry.npmmirror.com/ieee754@1.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/buffer@6.0.3:
|
||||||
|
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz}
|
||||||
|
name: buffer
|
||||||
|
version: 6.0.3
|
||||||
|
dependencies:
|
||||||
|
base64-js: registry.npmmirror.com/base64-js@1.5.1
|
||||||
|
ieee754: registry.npmmirror.com/ieee754@1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/builtin-status-codes@3.0.0:
|
registry.npmmirror.com/builtin-status-codes@3.0.0:
|
||||||
resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz}
|
resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz}
|
||||||
name: builtin-status-codes
|
name: builtin-status-codes
|
||||||
@@ -4032,6 +4070,15 @@ packages:
|
|||||||
randombytes: registry.npmmirror.com/randombytes@2.1.0
|
randombytes: registry.npmmirror.com/randombytes@2.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/dns-packet@5.6.1:
|
||||||
|
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dns-packet/-/dns-packet-5.6.1.tgz}
|
||||||
|
name: dns-packet
|
||||||
|
version: 5.6.1
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
'@leichtgewicht/ip-codec': registry.npmmirror.com/@leichtgewicht/ip-codec@2.0.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/doctrine@3.0.0:
|
registry.npmmirror.com/doctrine@3.0.0:
|
||||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz}
|
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz}
|
||||||
name: doctrine
|
name: doctrine
|
||||||
@@ -5859,7 +5906,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz}
|
||||||
name: ieee754
|
name: ieee754
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
registry.npmmirror.com/ignore-by-default@1.0.1:
|
registry.npmmirror.com/ignore-by-default@1.0.1:
|
||||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz}
|
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz}
|
||||||
@@ -5988,6 +6034,25 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/ip-address@9.0.5:
|
||||||
|
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz}
|
||||||
|
name: ip-address
|
||||||
|
version: 9.0.5
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
dependencies:
|
||||||
|
jsbn: registry.npmmirror.com/jsbn@1.1.0
|
||||||
|
sprintf-js: registry.npmmirror.com/sprintf-js@1.1.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/ip6addr@0.2.5:
|
||||||
|
resolution: {integrity: sha512-9RGGSB6Zc9Ox5DpDGFnJdIeF0AsqXzdH+FspCfPPaU/L/4tI6P+5lIoFUFm9JXs9IrJv1boqAaNCQmoDADTSKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ip6addr/-/ip6addr-0.2.5.tgz}
|
||||||
|
name: ip6addr
|
||||||
|
version: 0.2.5
|
||||||
|
dependencies:
|
||||||
|
assert-plus: registry.npmmirror.com/assert-plus@1.0.0
|
||||||
|
jsprim: registry.npmmirror.com/jsprim@2.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/ipaddr.js@1.9.1:
|
registry.npmmirror.com/ipaddr.js@1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz}
|
||||||
name: ipaddr.js
|
name: ipaddr.js
|
||||||
@@ -6524,6 +6589,12 @@ packages:
|
|||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/jsbn@1.1.0:
|
||||||
|
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz}
|
||||||
|
name: jsbn
|
||||||
|
version: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/jsesc@0.5.0:
|
registry.npmmirror.com/jsesc@0.5.0:
|
||||||
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz}
|
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz}
|
||||||
name: jsesc
|
name: jsesc
|
||||||
@@ -6556,6 +6627,12 @@ packages:
|
|||||||
version: 0.2.3
|
version: 0.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/json-schema@0.4.0:
|
||||||
|
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz}
|
||||||
|
name: json-schema
|
||||||
|
version: 0.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/json-stable-stringify-without-jsonify@1.0.1:
|
registry.npmmirror.com/json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
|
||||||
name: json-stable-stringify-without-jsonify
|
name: json-stable-stringify-without-jsonify
|
||||||
@@ -6595,6 +6672,24 @@ packages:
|
|||||||
verror: registry.npmmirror.com/verror@1.10.0
|
verror: registry.npmmirror.com/verror@1.10.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/jsprim@2.0.2:
|
||||||
|
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsprim/-/jsprim-2.0.2.tgz}
|
||||||
|
name: jsprim
|
||||||
|
version: 2.0.2
|
||||||
|
engines: {'0': node >=0.6.0}
|
||||||
|
dependencies:
|
||||||
|
assert-plus: registry.npmmirror.com/assert-plus@1.0.0
|
||||||
|
extsprintf: registry.npmmirror.com/extsprintf@1.3.0
|
||||||
|
json-schema: registry.npmmirror.com/json-schema@0.4.0
|
||||||
|
verror: registry.npmmirror.com/verror@1.10.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/jsrsasign@11.1.0:
|
||||||
|
resolution: {integrity: sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsrsasign/-/jsrsasign-11.1.0.tgz}
|
||||||
|
name: jsrsasign
|
||||||
|
version: 11.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/just-debounce@1.1.0:
|
registry.npmmirror.com/just-debounce@1.1.0:
|
||||||
resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz}
|
resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz}
|
||||||
name: just-debounce
|
name: just-debounce
|
||||||
@@ -6943,6 +7038,16 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/maxmind@4.3.19:
|
||||||
|
resolution: {integrity: sha512-Bu/VEN7ZWAOCjifdZaXJQuN6/yO7+OK35pnJsqmz8sOndK3KQFvJoY+6HX09/MmLLqtCfa+sMK0iaQOaTejGNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/maxmind/-/maxmind-4.3.19.tgz}
|
||||||
|
name: maxmind
|
||||||
|
version: 4.3.19
|
||||||
|
engines: {node: '>=12', npm: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
mmdb-lib: registry.npmmirror.com/mmdb-lib@2.1.0
|
||||||
|
tiny-lru: registry.npmmirror.com/tiny-lru@11.2.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/md5.js@1.3.5:
|
registry.npmmirror.com/md5.js@1.3.5:
|
||||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz}
|
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz}
|
||||||
name: md5.js
|
name: md5.js
|
||||||
@@ -7132,6 +7237,13 @@ packages:
|
|||||||
version: 0.5.3
|
version: 0.5.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/mmdb-lib@2.1.0:
|
||||||
|
resolution: {integrity: sha512-tdDTZmnI5G4UoSctv2KxM/3VQt2XRj4CmR5R4VsAWsOUcS3LysHR34wtixWm/pXxXdkBDuN92auxkC0T2+qd1Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mmdb-lib/-/mmdb-lib-2.1.0.tgz}
|
||||||
|
name: mmdb-lib
|
||||||
|
version: 2.1.0
|
||||||
|
engines: {node: '>=10', npm: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/mocha@10.0.0:
|
registry.npmmirror.com/mocha@10.0.0:
|
||||||
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz}
|
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz}
|
||||||
name: mocha
|
name: mocha
|
||||||
@@ -9002,6 +9114,12 @@ packages:
|
|||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/sprintf-js@1.1.3:
|
||||||
|
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz}
|
||||||
|
name: sprintf-js
|
||||||
|
version: 1.1.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/sshpk@1.16.1:
|
registry.npmmirror.com/sshpk@1.16.1:
|
||||||
resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sshpk/-/sshpk-1.16.1.tgz}
|
resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sshpk/-/sshpk-1.16.1.tgz}
|
||||||
name: sshpk
|
name: sshpk
|
||||||
@@ -9424,6 +9542,13 @@ packages:
|
|||||||
process: registry.npmmirror.com/process@0.11.10
|
process: registry.npmmirror.com/process@0.11.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/tiny-lru@11.2.6:
|
||||||
|
resolution: {integrity: sha512-0PU3c9PjMnltZaFo2sGYv/nnJsMjG0Cxx8X6FXHPPGjFyoo1SJDxvUXW1207rdiSxYizf31roo+GrkIByQeZoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tiny-lru/-/tiny-lru-11.2.6.tgz}
|
||||||
|
name: tiny-lru
|
||||||
|
version: 11.2.6
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/tinyify@3.0.0:
|
registry.npmmirror.com/tinyify@3.0.0:
|
||||||
resolution: {integrity: sha512-RtjVjC1xwwxt8AMVfxEmo+FzRJB6p5sAOtFaJj8vMrkWShtArsM4dLVRWhx2Vc07Me3NWgmP7pi9UPm/a2XNNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinyify/-/tinyify-3.0.0.tgz}
|
resolution: {integrity: sha512-RtjVjC1xwwxt8AMVfxEmo+FzRJB6p5sAOtFaJj8vMrkWShtArsM4dLVRWhx2Vc07Me3NWgmP7pi9UPm/a2XNNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinyify/-/tinyify-3.0.0.tgz}
|
||||||
name: tinyify
|
name: tinyify
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Buffer } from 'buffer';
|
||||||
|
import rs from '@/utils/rs';
|
||||||
import YAML from '@/utils/yaml';
|
import YAML from '@/utils/yaml';
|
||||||
import download from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
import {
|
import {
|
||||||
@@ -5,7 +7,7 @@ import {
|
|||||||
isIPv6,
|
isIPv6,
|
||||||
isValidPortNumber,
|
isValidPortNumber,
|
||||||
isNotBlank,
|
isNotBlank,
|
||||||
utf8ArrayToStr,
|
ipAddress,
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
||||||
import PROXY_PREPROCESSORS from './preprocessors';
|
import PROXY_PREPROCESSORS from './preprocessors';
|
||||||
@@ -15,7 +17,7 @@ import $ from '@/core/app';
|
|||||||
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { getFlag, getISO } from '@/utils/geo';
|
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
@@ -83,7 +85,7 @@ async function processFn(proxies, operators = [], targetPlatform, source) {
|
|||||||
const { mode, content } = item.args;
|
const { mode, content } = item.args;
|
||||||
if (mode === 'link') {
|
if (mode === 'link') {
|
||||||
let noCache;
|
let noCache;
|
||||||
let url = content;
|
let url = content || '';
|
||||||
if (url.endsWith('#noCache')) {
|
if (url.endsWith('#noCache')) {
|
||||||
url = url.replace(/#noCache$/, '');
|
url = url.replace(/#noCache$/, '');
|
||||||
noCache = true;
|
noCache = true;
|
||||||
@@ -199,6 +201,10 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
proxies = proxies.map((proxy) => {
|
proxies = proxies.map((proxy) => {
|
||||||
|
proxy._subName = proxy.subName;
|
||||||
|
proxy._collectionName = proxy.collectionName;
|
||||||
|
proxy._resolved = proxy.resolved;
|
||||||
|
|
||||||
if (!isNotBlank(proxy.name)) {
|
if (!isNotBlank(proxy.name)) {
|
||||||
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||||
}
|
}
|
||||||
@@ -267,12 +273,15 @@ export const ProxyUtils = {
|
|||||||
parse,
|
parse,
|
||||||
process: processFn,
|
process: processFn,
|
||||||
produce,
|
produce,
|
||||||
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
isIP,
|
isIP,
|
||||||
yaml: YAML,
|
yaml: YAML,
|
||||||
getFlag,
|
getFlag,
|
||||||
|
removeFlag,
|
||||||
getISO,
|
getISO,
|
||||||
|
MMDB,
|
||||||
Gist,
|
Gist,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,7 +341,11 @@ function lastParse(proxy) {
|
|||||||
proxy.network = 'tcp';
|
proxy.network = 'tcp';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
@@ -401,7 +414,30 @@ function lastParse(proxy) {
|
|||||||
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) {
|
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) {
|
||||||
delete proxy.ports;
|
delete proxy.ports;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
['hysteria2'].includes(proxy.type) &&
|
||||||
|
proxy.obfs &&
|
||||||
|
!['salamander'].includes(proxy.obfs) &&
|
||||||
|
!proxy['obfs-password']
|
||||||
|
) {
|
||||||
|
proxy['obfs-password'] = proxy.obfs;
|
||||||
|
proxy.obfs = 'salamander';
|
||||||
|
}
|
||||||
if (['vless'].includes(proxy.type)) {
|
if (['vless'].includes(proxy.type)) {
|
||||||
|
// 删除 reality-opts: {}
|
||||||
|
if (
|
||||||
|
proxy['reality-opts'] &&
|
||||||
|
Object.keys(proxy['reality-opts']).length === 0
|
||||||
|
) {
|
||||||
|
delete proxy['reality-opts'];
|
||||||
|
}
|
||||||
|
// 删除 grpc-opts: {}
|
||||||
|
if (
|
||||||
|
proxy['grpc-opts'] &&
|
||||||
|
Object.keys(proxy['grpc-opts']).length === 0
|
||||||
|
) {
|
||||||
|
delete proxy['grpc-opts'];
|
||||||
|
}
|
||||||
// 非 reality, 空 flow 没有意义
|
// 非 reality, 空 flow 没有意义
|
||||||
if (!proxy['reality-opts'] && !proxy.flow) {
|
if (!proxy['reality-opts'] && !proxy.flow) {
|
||||||
delete proxy.flow;
|
delete proxy.flow;
|
||||||
@@ -416,6 +452,7 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof proxy.name !== 'string') {
|
if (typeof proxy.name !== 'string') {
|
||||||
if (/^\d+$/.test(proxy.name)) {
|
if (/^\d+$/.test(proxy.name)) {
|
||||||
proxy.name = `${proxy.name}`;
|
proxy.name = `${proxy.name}`;
|
||||||
@@ -424,7 +461,7 @@ function lastParse(proxy) {
|
|||||||
if (proxy.name?.data) {
|
if (proxy.name?.data) {
|
||||||
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
||||||
} else {
|
} else {
|
||||||
proxy.name = utf8ArrayToStr(proxy.name);
|
proxy.name = Buffer.from(proxy.name).toString('utf8');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(`proxy.name decode failed\nReason: ${e}`);
|
$.error(`proxy.name decode failed\nReason: ${e}`);
|
||||||
@@ -432,9 +469,46 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (['ws', 'http', 'h2'].includes(proxy.network)) {
|
||||||
|
if (
|
||||||
|
['ws', 'h2'].includes(proxy.network) &&
|
||||||
|
!proxy[`${proxy.network}-opts`]?.path
|
||||||
|
) {
|
||||||
|
proxy[`${proxy.network}-opts`] =
|
||||||
|
proxy[`${proxy.network}-opts`] || {};
|
||||||
|
proxy[`${proxy.network}-opts`].path = '/';
|
||||||
|
} else if (
|
||||||
|
proxy.network === 'http' &&
|
||||||
|
(!Array.isArray(proxy[`${proxy.network}-opts`]?.path) ||
|
||||||
|
proxy[`${proxy.network}-opts`]?.path.every((i) => !i))
|
||||||
|
) {
|
||||||
|
proxy[`${proxy.network}-opts`] =
|
||||||
|
proxy[`${proxy.network}-opts`] || {};
|
||||||
|
proxy[`${proxy.network}-opts`].path = ['/'];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (['', 'off'].includes(proxy.sni)) {
|
if (['', 'off'].includes(proxy.sni)) {
|
||||||
proxy['disable-sni'] = true;
|
proxy['disable-sni'] = true;
|
||||||
}
|
}
|
||||||
|
let caStr = proxy['ca_str'];
|
||||||
|
if (proxy['ca-str']) {
|
||||||
|
caStr = proxy['ca-str'];
|
||||||
|
} else if (caStr) {
|
||||||
|
delete proxy['ca_str'];
|
||||||
|
proxy['ca-str'] = caStr;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if ($.env.isNode && !caStr && proxy['_ca']) {
|
||||||
|
caStr = $.node.fs.readFileSync(proxy['_ca'], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`Read ca file failed\nReason: ${e}`);
|
||||||
|
}
|
||||||
|
if (!proxy['tls-fingerprint'] && caStr) {
|
||||||
|
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Base64 } from 'js-base64';
|
|||||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
function URI_SS() {
|
function URI_SS() {
|
||||||
|
// TODO: 暂不支持 httpupgrade
|
||||||
const name = 'URI SS Parser';
|
const name = 'URI SS Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
return /^ss:\/\//.test(line);
|
return /^ss:\/\//.test(line);
|
||||||
@@ -299,16 +300,26 @@ function URI_VMess() {
|
|||||||
if (proxy.tls && proxy.sni) {
|
if (proxy.tls && proxy.sni) {
|
||||||
proxy.sni = params.sni;
|
proxy.sni = params.sni;
|
||||||
}
|
}
|
||||||
|
let httpupgrade = false;
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (params.net === 'ws' || params.obfs === 'websocket') {
|
if (params.net === 'ws' || params.obfs === 'websocket') {
|
||||||
proxy.network = 'ws';
|
proxy.network = 'ws';
|
||||||
} else if (
|
} else if (
|
||||||
['tcp', 'http'].includes(params.net) ||
|
['http'].includes(params.net) ||
|
||||||
params.obfs === 'http'
|
['http'].includes(params.obfs) ||
|
||||||
|
['http'].includes(params.type)
|
||||||
) {
|
) {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
} else if (['grpc'].includes(params.net)) {
|
} else if (['grpc'].includes(params.net)) {
|
||||||
proxy.network = 'grpc';
|
proxy.network = 'grpc';
|
||||||
|
} else if (
|
||||||
|
params.net === 'httpupgrade' ||
|
||||||
|
proxy.network === 'httpupgrade'
|
||||||
|
) {
|
||||||
|
proxy.network = 'ws';
|
||||||
|
httpupgrade = true;
|
||||||
|
} else if (params.net === 'h2' || proxy.network === 'h2') {
|
||||||
|
proxy.network = 'h2';
|
||||||
}
|
}
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
let transportHost = params.host ?? params.obfsParam;
|
let transportHost = params.host ?? params.obfsParam;
|
||||||
@@ -324,6 +335,10 @@ function URI_VMess() {
|
|||||||
|
|
||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
if (transportHost) {
|
if (transportHost) {
|
||||||
|
// 1)http(tcp)->host中间逗号(,)隔开
|
||||||
|
transportHost = transportHost
|
||||||
|
.split(',')
|
||||||
|
.map((i) => i.trim());
|
||||||
transportHost = Array.isArray(transportHost)
|
transportHost = Array.isArray(transportHost)
|
||||||
? transportHost[0]
|
? transportHost[0]
|
||||||
: transportHost;
|
: transportHost;
|
||||||
@@ -332,6 +347,8 @@ function URI_VMess() {
|
|||||||
transportPath = Array.isArray(transportPath)
|
transportPath = Array.isArray(transportPath)
|
||||||
? transportPath[0]
|
? transportPath[0]
|
||||||
: transportPath;
|
: transportPath;
|
||||||
|
} else {
|
||||||
|
transportPath = '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (transportPath || transportHost) {
|
if (transportPath || transportHost) {
|
||||||
@@ -341,10 +358,15 @@ function URI_VMess() {
|
|||||||
'_grpc-type': getIfNotBlank(params.type),
|
'_grpc-type': getIfNotBlank(params.type),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
proxy[`${proxy.network}-opts`] = {
|
const opts = {
|
||||||
path: getIfNotBlank(transportPath),
|
path: getIfNotBlank(transportPath),
|
||||||
headers: { Host: getIfNotBlank(transportHost) },
|
headers: { Host: getIfNotBlank(transportHost) },
|
||||||
};
|
};
|
||||||
|
if (httpupgrade) {
|
||||||
|
opts['v2ray-http-upgrade'] = true;
|
||||||
|
opts['v2ray-http-upgrade-fast-open'] = true;
|
||||||
|
}
|
||||||
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
@@ -444,10 +466,13 @@ function URI_VLESS() {
|
|||||||
proxy[`${params.security}-opts`] = opts;
|
proxy[`${params.security}-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let httpupgrade = false;
|
||||||
proxy.network = params.type;
|
proxy.network = params.type;
|
||||||
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
|
} else if (proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws';
|
||||||
|
httpupgrade = true;
|
||||||
}
|
}
|
||||||
if (!proxy.network && isShadowrocket && params.obfs) {
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
proxy.network = params.obfs;
|
proxy.network = params.obfs;
|
||||||
@@ -485,6 +510,10 @@ function URI_VLESS() {
|
|||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
opts['_grpc-type'] = params.mode || 'gun';
|
opts['_grpc-type'] = params.mode || 'gun';
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
opts['v2ray-http-upgrade'] = true;
|
||||||
|
opts['v2ray-http-upgrade-fast-open'] = true;
|
||||||
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
@@ -831,6 +860,9 @@ function Clash_All() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['server-cert-fingerprint']) {
|
||||||
|
proxy['tls-fingerprint'] = proxy['server-cert-fingerprint'];
|
||||||
|
}
|
||||||
if (proxy.fingerprint) {
|
if (proxy.fingerprint) {
|
||||||
proxy['tls-fingerprint'] = proxy.fingerprint;
|
proxy['tls-fingerprint'] = proxy.fingerprint;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
|
|||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
@@ -178,6 +178,7 @@ udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
|||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
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'; }
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ trojan = tag equals "trojan"i address password (transport/transport_host/transpo
|
|||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleTransport();
|
handleTransport();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/ecn/others)* {
|
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
}
|
}
|
||||||
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
https = tag equals "https"i address (username password)? (tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
@@ -176,6 +176,7 @@ udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
|||||||
|
|
||||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
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'; }
|
||||||
|
|
||||||
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
tag = match:[^=,]* { proxy.name = match.join("").trim(); }
|
||||||
comma = _ "," _
|
comma = _ "," _
|
||||||
|
|||||||
@@ -89,7 +89,12 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params["type"]) {
|
if (params["type"]) {
|
||||||
|
let httpupgrade
|
||||||
proxy.network = params["type"]
|
proxy.network = params["type"]
|
||||||
|
if(proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws'
|
||||||
|
httpupgrade = true
|
||||||
|
}
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
proxy[proxy.network + '-opts'] = {
|
proxy[proxy.network + '-opts'] = {
|
||||||
'grpc-service-name': params["serviceName"],
|
'grpc-service-name': params["serviceName"],
|
||||||
@@ -102,6 +107,10 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
if (params["host"]) {
|
if (params["host"]) {
|
||||||
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,12 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params["type"]) {
|
if (params["type"]) {
|
||||||
|
let httpupgrade
|
||||||
proxy.network = params["type"]
|
proxy.network = params["type"]
|
||||||
|
if(proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws'
|
||||||
|
httpupgrade = true
|
||||||
|
}
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
proxy[proxy.network + '-opts'] = {
|
proxy[proxy.network + '-opts'] = {
|
||||||
'grpc-service-name': params["serviceName"],
|
'grpc-service-name': params["serviceName"],
|
||||||
@@ -100,6 +105,10 @@ params = "?" head:param tail:("&"@param)* {
|
|||||||
if (params["host"]) {
|
if (params["host"]) {
|
||||||
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function Base64Encoded() {
|
|||||||
'aHR0c', // htt
|
'aHR0c', // htt
|
||||||
'dmxlc3M=', // vless
|
'dmxlc3M=', // vless
|
||||||
'aHlzdGVyaWEy', // hysteria2
|
'aHlzdGVyaWEy', // hysteria2
|
||||||
|
'aHkyOi8v', // hy2://
|
||||||
'd2lyZWd1YXJkOi8v', // wireguard://
|
'd2lyZWd1YXJkOi8v', // wireguard://
|
||||||
'd2c6Ly8=', // wg://
|
'd2c6Ly8=', // wg://
|
||||||
'dHVpYzovLw==', // tuic://
|
'dHVpYzovLw==', // tuic://
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
import scriptResourceCache from '@/utils/script-resource-cache';
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6, ipAddress } from '@/utils';
|
||||||
import { FULL } from '@/utils/logical';
|
import { FULL } from '@/utils/logical';
|
||||||
import { getFlag } from '@/utils/geo';
|
import { getFlag, removeFlag } from '@/utils/geo';
|
||||||
|
import { doh } from '@/utils/dns';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
|
|
||||||
import env from '@/utils/env';
|
import env from '@/utils/env';
|
||||||
import {
|
import {
|
||||||
@@ -362,9 +364,6 @@ function parseIP4P(IP4P) {
|
|||||||
let server;
|
let server;
|
||||||
let port;
|
let port;
|
||||||
try {
|
try {
|
||||||
if (!/^2001::[^:]+:[^:]+:[^:]+$/.test(IP4P)) {
|
|
||||||
throw new Error(`Invalid IP4P: ${IP4P}`);
|
|
||||||
}
|
|
||||||
let array = IP4P.split(':');
|
let array = IP4P.split(':');
|
||||||
|
|
||||||
port = parseInt(array[2], 16);
|
port = parseInt(array[2], 16);
|
||||||
@@ -389,31 +388,59 @@ function parseIP4P(IP4P) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DOMAIN_RESOLVERS = {
|
const DOMAIN_RESOLVERS = {
|
||||||
Google: async function (domain, type, noCache) {
|
Custom: async function (domain, type, noCache, timeout, edns, url) {
|
||||||
|
const id = hex_md5(`CUSTOM:${url}:${domain}:${type}`);
|
||||||
|
const cached = resourceCache.get(id);
|
||||||
|
if (!noCache && cached) return cached;
|
||||||
|
const res = await doh({
|
||||||
|
url,
|
||||||
|
domain,
|
||||||
|
type: type === 'IPv6' ? 'AAAA' : 'A',
|
||||||
|
timeout,
|
||||||
|
edns,
|
||||||
|
});
|
||||||
|
const { answers } = res;
|
||||||
|
if (!Array.isArray(answers) || answers.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
const result = answers.map((i) => i?.data).filter((i) => i);
|
||||||
|
if (result.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
resourceCache.set(id, result);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
Google: async function (domain, type, noCache, timeout, edns) {
|
||||||
const id = hex_md5(`GOOGLE:${domain}:${type}`);
|
const id = hex_md5(`GOOGLE:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!noCache && cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
|
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}`,
|
)}&type=${
|
||||||
|
type === 'IPv6' ? 'AAAA' : 'A'
|
||||||
|
}&edns_client_subnet=${edns}`,
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/dns-json',
|
accept: 'application/dns-json',
|
||||||
},
|
},
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
if (body['Status'] !== 0) {
|
if (body['Status'] !== 0) {
|
||||||
throw new Error(`Status is ${body['Status']}`);
|
throw new Error(`Status is ${body['Status']}`);
|
||||||
}
|
}
|
||||||
const answers = body['Answer'];
|
const answers = body['Answer'];
|
||||||
if (answers.length === 0) {
|
if (!Array.isArray(answers) || answers.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
const result = answers.map((i) => i?.data).filter((i) => i);
|
||||||
|
if (result.length === 0) {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1].data;
|
|
||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
'IP-API': async function (domain, type, noCache) {
|
'IP-API': async function (domain, type, noCache, timeout) {
|
||||||
if (['IPv6'].includes(type)) {
|
if (['IPv6'].includes(type)) {
|
||||||
throw new Error(`域名解析服务提供方 IP-API 不支持 ${type}`);
|
throw new Error(`域名解析服务提供方 IP-API 不支持 ${type}`);
|
||||||
}
|
}
|
||||||
@@ -424,16 +451,23 @@ const DOMAIN_RESOLVERS = {
|
|||||||
url: `http://ip-api.com/json/${encodeURIComponent(
|
url: `http://ip-api.com/json/${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
)}?lang=zh-CN`,
|
)}?lang=zh-CN`,
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
if (body['status'] !== 'success') {
|
if (body['status'] !== 'success') {
|
||||||
throw new Error(`Status is ${body['status']}`);
|
throw new Error(`Status is ${body['status']}`);
|
||||||
}
|
}
|
||||||
const result = body.query;
|
if (!body.query || body.query === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
const result = [body.query];
|
||||||
|
if (result.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Cloudflare: async function (domain, type, noCache) {
|
Cloudflare: async function (domain, type, noCache, timeout) {
|
||||||
const id = hex_md5(`CLOUDFLARE:${domain}:${type}`);
|
const id = hex_md5(`CLOUDFLARE:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!noCache && cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
@@ -444,80 +478,112 @@ const DOMAIN_RESOLVERS = {
|
|||||||
headers: {
|
headers: {
|
||||||
accept: 'application/dns-json',
|
accept: 'application/dns-json',
|
||||||
},
|
},
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
if (body['Status'] !== 0) {
|
if (body['Status'] !== 0) {
|
||||||
throw new Error(`Status is ${body['Status']}`);
|
throw new Error(`Status is ${body['Status']}`);
|
||||||
}
|
}
|
||||||
const answers = body['Answer'];
|
const answers = body['Answer'];
|
||||||
if (answers.length === 0) {
|
if (!Array.isArray(answers) || answers.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
const result = answers.map((i) => i?.data).filter((i) => i);
|
||||||
|
if (result.length === 0) {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1].data;
|
|
||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Ali: async function (domain, type, noCache) {
|
Ali: async function (domain, type, noCache, timeout, edns) {
|
||||||
const id = hex_md5(`ALI:${domain}:${type}`);
|
const id = hex_md5(`ALI:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!noCache && cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `http://223.6.6.6/resolve?name=${encodeURIComponent(
|
url: `http://223.6.6.6/resolve?edns_client_subnet=${edns}/24&name=${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
|
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/dns-json',
|
accept: 'application/dns-json',
|
||||||
},
|
},
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
const answers = JSON.parse(resp.body);
|
const answers = JSON.parse(resp.body);
|
||||||
if (answers.length === 0) {
|
if (!Array.isArray(answers) || answers.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
|
const result = answers;
|
||||||
|
if (result.length === 0) {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1];
|
|
||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Tencent: async function (domain, type, noCache) {
|
Tencent: async function (domain, type, noCache, timeout, edns) {
|
||||||
const id = hex_md5(`ALI:${domain}:${type}`);
|
const id = hex_md5(`TENCENT:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!noCache && cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `http://119.28.28.28/d?type=${
|
url: `http://119.28.28.28/d?ip=${edns}&type=${
|
||||||
type === 'IPv6' ? 'AAAA' : 'A'
|
type === 'IPv6' ? 'AAAA' : 'A'
|
||||||
}&dn=${encodeURIComponent(domain)}`,
|
}&dn=${encodeURIComponent(domain)}`,
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/dns-json',
|
accept: 'application/dns-json',
|
||||||
},
|
},
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
||||||
if (answers.length === 0 || String(answers) === '0') {
|
if (answers.length === 0 || String(answers) === '0') {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1];
|
const result = answers;
|
||||||
|
if (result.length === 0) {
|
||||||
|
throw new Error('No answers');
|
||||||
|
}
|
||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
function ResolveDomainOperator({
|
||||||
|
provider,
|
||||||
|
type: _type,
|
||||||
|
filter,
|
||||||
|
cache,
|
||||||
|
url,
|
||||||
|
timeout,
|
||||||
|
edns: _edns,
|
||||||
|
}) {
|
||||||
if (['IPv6', 'IP4P'].includes(_type) && ['IP-API'].includes(provider)) {
|
if (['IPv6', 'IP4P'].includes(_type) && ['IP-API'].includes(provider)) {
|
||||||
throw new Error(`域名解析服务提供方 ${provider} 不支持 ${_type}`);
|
throw new Error(`域名解析服务提供方 ${provider} 不支持 ${_type}`);
|
||||||
}
|
}
|
||||||
|
const { defaultTimeout } = $.read(SETTINGS_KEY);
|
||||||
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
let type = ['IPv6', 'IP4P'].includes(_type) ? 'IPv6' : 'IPv4';
|
let type = ['IPv6', 'IP4P'].includes(_type) ? 'IPv6' : 'IPv4';
|
||||||
|
|
||||||
const resolver = DOMAIN_RESOLVERS[provider];
|
const resolver = DOMAIN_RESOLVERS[provider];
|
||||||
if (!resolver) {
|
if (!resolver) {
|
||||||
throw new Error(`找不到域名解析服务提供方: ${provider}`);
|
throw new Error(`找不到域名解析服务提供方: ${provider}`);
|
||||||
}
|
}
|
||||||
|
let edns = _edns || '223.6.6.6';
|
||||||
|
if (!isIP(edns)) throw new Error(`域名解析 EDNS 应为 IP`);
|
||||||
|
$.info(
|
||||||
|
`Domain Resolver: [${_type}] ${provider} ${edns || ''} ${url || ''}`,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
name: 'Resolve Domain Operator',
|
name: 'Resolve Domain Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
|
proxies.forEach((p, i) => {
|
||||||
|
if (!p['_no-resolve'] && p['no-resolve']) {
|
||||||
|
proxies[i]['_no-resolve'] = p['no-resolve'];
|
||||||
|
}
|
||||||
|
});
|
||||||
const results = {};
|
const results = {};
|
||||||
const limit = 15; // more than 20 concurrency may result in surge TCP connection shortage.
|
const limit = 15; // more than 20 concurrency may result in surge TCP connection shortage.
|
||||||
const totalDomain = [
|
const totalDomain = [
|
||||||
...new Set(
|
...new Set(
|
||||||
proxies
|
proxies
|
||||||
.filter((p) => !isIP(p.server) && !p['no-resolve'])
|
.filter((p) => !isIP(p.server) && !p['_no-resolve'])
|
||||||
.map((c) => c.server),
|
.map((c) => c.server),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -526,7 +592,14 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
|||||||
const currentBatch = [];
|
const currentBatch = [];
|
||||||
for (let domain of totalDomain.splice(0, limit)) {
|
for (let domain of totalDomain.splice(0, limit)) {
|
||||||
currentBatch.push(
|
currentBatch.push(
|
||||||
resolver(domain, type, cache === 'disabled')
|
resolver(
|
||||||
|
domain,
|
||||||
|
type,
|
||||||
|
cache === 'disabled',
|
||||||
|
requestTimeout,
|
||||||
|
edns,
|
||||||
|
url,
|
||||||
|
)
|
||||||
.then((ip) => {
|
.then((ip) => {
|
||||||
results[domain] = ip;
|
results[domain] = ip;
|
||||||
$.info(
|
$.info(
|
||||||
@@ -543,34 +616,58 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
|||||||
await Promise.all(currentBatch);
|
await Promise.all(currentBatch);
|
||||||
}
|
}
|
||||||
proxies.forEach((p) => {
|
proxies.forEach((p) => {
|
||||||
if (!p['no-resolve']) {
|
if (!p['_no-resolve']) {
|
||||||
if (results[p.server]) {
|
if (results[p.server]) {
|
||||||
if (_type === 'IP4P') {
|
p._resolved_ips = results[p.server];
|
||||||
const { server, port } = parseIP4P(
|
let ip = Array.isArray(results[p.server])
|
||||||
results[p.server],
|
? results[p.server][
|
||||||
);
|
Math.floor(
|
||||||
if (server && port) {
|
Math.random() * results[p.server].length,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
: results[p.server];
|
||||||
|
if (type === 'IPv6' && isIPv6(ip)) {
|
||||||
|
try {
|
||||||
|
ip = new ipAddress.Address6(ip).correctForm();
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`Failed to parse IPv6 address: ${ip}: ${e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (/^2001::[^:]+:[^:]+:[^:]+$/.test(ip)) {
|
||||||
|
p._IP4P = ip;
|
||||||
|
const { server, port } = parseIP4P(ip);
|
||||||
|
if (server && port) {
|
||||||
|
p._domain = p.server;
|
||||||
|
p.server = server;
|
||||||
|
p.port = port;
|
||||||
|
p.resolved = true;
|
||||||
|
p._IPv4 = p.server;
|
||||||
|
if (!isIP(p._IP)) {
|
||||||
|
p._IP = p.server;
|
||||||
|
}
|
||||||
|
} else if (!p.resolved) {
|
||||||
|
p.resolved = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
p._domain = p.server;
|
p._domain = p.server;
|
||||||
p.server = server;
|
p.server = ip;
|
||||||
p.port = port;
|
|
||||||
p.resolved = true;
|
p.resolved = true;
|
||||||
p._IPv4 = p.server;
|
p[`_${type}`] = p.server;
|
||||||
if (!isIP(p._IP)) {
|
if (!isIP(p._IP)) {
|
||||||
p._IP = p.server;
|
p._IP = p.server;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
p.resolved = false;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p._domain = p.server;
|
p._domain = p.server;
|
||||||
p.server = results[p.server];
|
p.server = ip;
|
||||||
p.resolved = true;
|
p.resolved = true;
|
||||||
p[`_${type}`] = p.server;
|
p[`_${type}`] = p.server;
|
||||||
if (!isIP(p._IP)) {
|
if (!isIP(p._IP)) {
|
||||||
p._IP = p.server;
|
p._IP = p.server;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!p.resolved) {
|
||||||
p.resolved = false;
|
p.resolved = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,7 +675,7 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
|||||||
|
|
||||||
return proxies.filter((p) => {
|
return proxies.filter((p) => {
|
||||||
if (filter === 'removeFailed') {
|
if (filter === 'removeFailed') {
|
||||||
return isIP(p.server) || p['no-resolve'] || p.resolved;
|
return isIP(p.server) || p['_no-resolve'] || p.resolved;
|
||||||
} else if (filter === 'IPOnly') {
|
} else if (filter === 'IPOnly') {
|
||||||
return isIP(p.server);
|
return isIP(p.server);
|
||||||
} else if (filter === 'IPv4Only') {
|
} else if (filter === 'IPv4Only') {
|
||||||
@@ -864,13 +961,6 @@ function clone(object) {
|
|||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove flag
|
|
||||||
function removeFlag(str) {
|
|
||||||
return str
|
|
||||||
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]|🏴☠️|🏳️🌈/g, '')
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDynamicFunction(name, script, $arguments) {
|
function createDynamicFunction(name, script, $arguments) {
|
||||||
const flowUtils = {
|
const flowUtils = {
|
||||||
getFlowField,
|
getFlowField,
|
||||||
|
|||||||
@@ -133,9 +133,13 @@ export default function Clash_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -158,6 +162,7 @@ export default function Clash_Producer() {
|
|||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -149,9 +149,13 @@ export default function ClashMeta_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -173,6 +177,7 @@ export default function ClashMeta_Producer() {
|
|||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -408,8 +408,8 @@ function wireguard(proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hysteria2(proxy) {
|
function hysteria2(proxy) {
|
||||||
if (proxy.obfs || proxy['obfs-password']) {
|
if (proxy['obfs-password'] && proxy.obfs != 'salamander') {
|
||||||
throw new Error(`obfs is unsupported`);
|
throw new Error(`only salamander obfs is supported`);
|
||||||
}
|
}
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=Hysteria2,${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=Hysteria2,${proxy.server},${proxy.port}`);
|
||||||
@@ -423,6 +423,10 @@ function hysteria2(proxy) {
|
|||||||
'skip-cert-verify',
|
'skip-cert-verify',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||||
|
result.append(`,salamander-password=${proxy['obfs-password']}`);
|
||||||
|
}
|
||||||
|
|
||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
|
|||||||
@@ -152,9 +152,13 @@ export default function ShadowRocket_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -176,6 +180,7 @@ export default function ShadowRocket_Producer() {
|
|||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -202,8 +202,8 @@ const tlsParser = (proxy, parsedProxy) => {
|
|||||||
parsedProxy.tls.alpn = [proxy.alpn];
|
parsedProxy.tls.alpn = [proxy.alpn];
|
||||||
} else if (Array.isArray(proxy.alpn)) parsedProxy.tls.alpn = proxy.alpn;
|
} else if (Array.isArray(proxy.alpn)) parsedProxy.tls.alpn = proxy.alpn;
|
||||||
if (proxy.ca) parsedProxy.tls.certificate_path = `${proxy.ca}`;
|
if (proxy.ca) parsedProxy.tls.certificate_path = `${proxy.ca}`;
|
||||||
if (proxy.ca_str) parsedProxy.tls.certificate = proxy.ca_sStr;
|
if (proxy.ca_str) parsedProxy.tls.certificate = [proxy.ca_str];
|
||||||
if (proxy['ca-str']) parsedProxy.tls.certificate = proxy['ca-str'];
|
if (proxy['ca-str']) parsedProxy.tls.certificate = [proxy['ca-str']];
|
||||||
if (proxy['client-fingerprint'] && proxy['client-fingerprint'] !== '')
|
if (proxy['client-fingerprint'] && proxy['client-fingerprint'] !== '')
|
||||||
parsedProxy.tls.utls = {
|
parsedProxy.tls.utls = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export default function Stash_Producer() {
|
|||||||
'wireguard',
|
'wireguard',
|
||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
|
'ssh',
|
||||||
|
'juicity',
|
||||||
].includes(proxy.type) ||
|
].includes(proxy.type) ||
|
||||||
(proxy.type === 'ss' &&
|
(proxy.type === 'ss' &&
|
||||||
![
|
![
|
||||||
@@ -232,14 +234,18 @@ export default function Stash_Producer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy['server-cert-fingerprint'] = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
@@ -265,6 +271,7 @@ export default function Stash_Producer() {
|
|||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const targetPlatform = 'Surfboard';
|
|||||||
|
|
||||||
export default function Surfboard_Producer() {
|
export default function Surfboard_Producer() {
|
||||||
const produce = (proxy) => {
|
const produce = (proxy) => {
|
||||||
proxy.name = proxy.name.replace(/=/g, '');
|
proxy.name = proxy.name.replace(/=|,/g, '');
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy);
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ const ipVersions = {
|
|||||||
|
|
||||||
export default function Surge_Producer() {
|
export default function Surge_Producer() {
|
||||||
const produce = (proxy, type, opts = {}) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
|
proxy.name = proxy.name.replace(/=|,/g, '');
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
return trojan(proxy);
|
return trojan(proxy);
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
return vmess(proxy);
|
return vmess(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'http':
|
case 'http':
|
||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'socks5':
|
case 'socks5':
|
||||||
@@ -263,7 +264,7 @@ function trojan(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmess(proxy) {
|
function vmess(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,username=${proxy.uuid}`, 'uuid');
|
result.appendIfPresent(`,username=${proxy.uuid}`, 'uuid');
|
||||||
@@ -277,7 +278,7 @@ function vmess(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// transport
|
// transport
|
||||||
handleTransport(result, proxy);
|
handleTransport(result, proxy, includeUnsupportedProxy);
|
||||||
|
|
||||||
// AEAD
|
// AEAD
|
||||||
if (isPresent(proxy, 'aead')) {
|
if (isPresent(proxy, 'aead')) {
|
||||||
@@ -1012,7 +1013,7 @@ function hysteria2(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTransport(result, proxy) {
|
function handleTransport(result, proxy, includeUnsupportedProxy) {
|
||||||
if (isPresent(proxy, 'network')) {
|
if (isPresent(proxy, 'network')) {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
result.append(`,ws=true`);
|
result.append(`,ws=true`);
|
||||||
@@ -1038,7 +1039,13 @@ function handleTransport(result, proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`network ${proxy.network} is unsupported`);
|
if (includeUnsupportedProxy && ['http'].includes(proxy.network)) {
|
||||||
|
$.info(
|
||||||
|
`Include Unsupported Proxy: nework ${proxy.network} -> tcp`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`network ${proxy.network} is unsupported`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,17 @@ export default function URI_Producer() {
|
|||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
delete proxy.id;
|
delete proxy.id;
|
||||||
delete proxy.resolved;
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
for (const key in proxy) {
|
for (const key in proxy) {
|
||||||
if (proxy[key] == null || /^_/i.test(key)) {
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
delete proxy[key];
|
delete proxy[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
if (
|
||||||
|
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
||||||
|
proxy.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
if (proxy.server && isIPv6(proxy.server)) {
|
if (proxy.server && isIPv6(proxy.server)) {
|
||||||
@@ -84,6 +89,11 @@ export default function URI_Producer() {
|
|||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
net = 'tcp';
|
net = 'tcp';
|
||||||
type = 'http';
|
type = 'http';
|
||||||
|
} else if (
|
||||||
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
net = 'httpupgrade';
|
||||||
}
|
}
|
||||||
result = {
|
result = {
|
||||||
v: '2',
|
v: '2',
|
||||||
@@ -172,9 +182,15 @@ export default function URI_Producer() {
|
|||||||
if (proxy.flow) {
|
if (proxy.flow) {
|
||||||
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
||||||
}
|
}
|
||||||
let vlessTransport = `&type=${encodeURIComponent(
|
let vlessType = proxy.network;
|
||||||
proxy.network,
|
if (
|
||||||
)}`;
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
vlessType = 'httpupgrade';
|
||||||
|
}
|
||||||
|
|
||||||
|
let vlessTransport = `&type=${encodeURIComponent(vlessType)}`;
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
// https://github.com/XTLS/Xray-core/issues/91
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
vlessTransport += `&mode=${encodeURIComponent(
|
vlessTransport += `&mode=${encodeURIComponent(
|
||||||
@@ -220,7 +236,14 @@ export default function URI_Producer() {
|
|||||||
case 'trojan':
|
case 'trojan':
|
||||||
let trojanTransport = '';
|
let trojanTransport = '';
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
trojanTransport = `&type=${proxy.network}`;
|
let trojanType = proxy.network;
|
||||||
|
if (
|
||||||
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
trojanType = 'httpupgrade';
|
||||||
|
}
|
||||||
|
trojanTransport = `&type=${encodeURIComponent(trojanType)}`;
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
let trojanTransportServiceName =
|
let trojanTransportServiceName =
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
|
|||||||
@@ -11,17 +11,65 @@ import { syncToGist } from '@/restful/artifacts';
|
|||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
|
|
||||||
!(async function () {
|
!(async function () {
|
||||||
const settings = $.read(SETTINGS_KEY);
|
let arg;
|
||||||
// if GitHub token is not configured
|
if (typeof $argument != 'undefined') {
|
||||||
if (!settings.githubUser || !settings.gistToken) return;
|
arg = Object.fromEntries(
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
$argument.split('&').map((item) => item.split('=')),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
arg = {};
|
||||||
|
}
|
||||||
|
let sub_names = (arg?.subscription ?? arg?.sub ?? '')
|
||||||
|
.split(/,|,/g)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length > 0)
|
||||||
|
.map((i) => decodeURIComponent(i));
|
||||||
|
let col_names = (arg?.collection ?? arg?.col ?? '')
|
||||||
|
.split(/,|,/g)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length > 0)
|
||||||
|
.map((i) => decodeURIComponent(i));
|
||||||
|
if (sub_names.length > 0 || col_names.length > 0) {
|
||||||
|
if (sub_names.length > 0)
|
||||||
|
await produceArtifacts(sub_names, 'subscription');
|
||||||
|
if (col_names.length > 0)
|
||||||
|
await produceArtifacts(col_names, 'collection');
|
||||||
|
} else {
|
||||||
|
const settings = $.read(SETTINGS_KEY);
|
||||||
|
// if GitHub token is not configured
|
||||||
|
if (!settings.githubUser || !settings.gistToken) return;
|
||||||
|
|
||||||
const artifacts = $.read(ARTIFACTS_KEY);
|
const artifacts = $.read(ARTIFACTS_KEY);
|
||||||
if (!artifacts || artifacts.length === 0) return;
|
if (!artifacts || artifacts.length === 0) return;
|
||||||
|
|
||||||
const shouldSync = artifacts.some((artifact) => artifact.sync);
|
const shouldSync = artifacts.some((artifact) => artifact.sync);
|
||||||
if (shouldSync) await doSync();
|
if (shouldSync) await doSync();
|
||||||
|
}
|
||||||
})().finally(() => $.done());
|
})().finally(() => $.done());
|
||||||
|
|
||||||
|
async function produceArtifacts(names, type) {
|
||||||
|
try {
|
||||||
|
if (names.length > 0) {
|
||||||
|
$.info(`produceArtifacts ${type} 开始: ${names.join(', ')}`);
|
||||||
|
await Promise.all(
|
||||||
|
names.map(async (name) => {
|
||||||
|
try {
|
||||||
|
await produceArtifact({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`${type} ${name} error: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
$.info(`produceArtifacts ${type} 完成: ${names.join(', ')}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`produceArtifacts error: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function doSync() {
|
async function doSync() {
|
||||||
console.log(
|
console.log(
|
||||||
`
|
`
|
||||||
@@ -69,6 +117,7 @@ async function doSync() {
|
|||||||
await produceArtifact({
|
await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name: subName,
|
name: subName,
|
||||||
|
awaitCustomCache: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// $.error(`${e.message ?? e}`);
|
// $.error(`${e.message ?? e}`);
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ async function downloadSubscription(req, res) {
|
|||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
|
||||||
$.info(`正在下载订阅:${name}`);
|
$.info(
|
||||||
|
`正在下载订阅:${name}\n请求 User-Agent: ${
|
||||||
|
req.headers['user-agent'] || req.headers['User-Agent']
|
||||||
|
}`,
|
||||||
|
);
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
ua,
|
ua,
|
||||||
@@ -119,10 +123,11 @@ async function downloadSubscription(req, res) {
|
|||||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
url = `${url || sub.url}`
|
url =
|
||||||
.split(/[\r\n]+/)
|
`${url || sub.url}`
|
||||||
.map((i) => i.trim())
|
.split(/[\r\n]+/)
|
||||||
.filter((i) => i.length)?.[0];
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0] || '';
|
||||||
|
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -152,6 +157,7 @@ async function downloadSubscription(req, res) {
|
|||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set('subscription-userinfo', flowInfo);
|
res.set('subscription-userinfo', flowInfo);
|
||||||
@@ -228,7 +234,11 @@ async function downloadCollection(req, res) {
|
|||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
|
|
||||||
$.info(`正在下载组合订阅:${name}`);
|
$.info(
|
||||||
|
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
||||||
|
req.headers['user-agent'] || req.headers['User-Agent']
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
@@ -274,10 +284,11 @@ async function downloadCollection(req, res) {
|
|||||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let url = `${sub.url}`
|
let url =
|
||||||
.split(/[\r\n]+/)
|
`${sub.url}`
|
||||||
.map((i) => i.trim())
|
.split(/[\r\n]+/)
|
||||||
.filter((i) => i.length)?.[0];
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0] || '';
|
||||||
|
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -306,6 +317,7 @@ async function downloadCollection(req, res) {
|
|||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
res.set('subscription-userinfo', flowInfo);
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
|
import { getFlowHeaders } from '@/utils/flow';
|
||||||
import { FILES_KEY } from '@/constants';
|
import { FILES_KEY } from '@/constants';
|
||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
@@ -50,7 +51,15 @@ async function getFile(req, res) {
|
|||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
|
|
||||||
$.info(`正在下载文件:${name}`);
|
$.info(`正在下载文件:${name}`);
|
||||||
let { url, ua, content, mergeSources, ignoreFailedRemoteFile } = req.query;
|
let {
|
||||||
|
url,
|
||||||
|
subInfoUrl,
|
||||||
|
subInfoUserAgent,
|
||||||
|
ua,
|
||||||
|
content,
|
||||||
|
mergeSources,
|
||||||
|
ignoreFailedRemoteFile,
|
||||||
|
} = req.query;
|
||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
$.info(`指定远程文件 URL: ${url}`);
|
$.info(`指定远程文件 URL: ${url}`);
|
||||||
@@ -59,6 +68,14 @@ async function getFile(req, res) {
|
|||||||
ua = decodeURIComponent(ua);
|
ua = decodeURIComponent(ua);
|
||||||
$.info(`指定远程文件 User-Agent: ${ua}`);
|
$.info(`指定远程文件 User-Agent: ${ua}`);
|
||||||
}
|
}
|
||||||
|
if (subInfoUrl) {
|
||||||
|
subInfoUrl = decodeURIComponent(subInfoUrl);
|
||||||
|
$.info(`指定获取流量的 subInfoUrl: ${subInfoUrl}`);
|
||||||
|
}
|
||||||
|
if (subInfoUserAgent) {
|
||||||
|
subInfoUserAgent = decodeURIComponent(subInfoUserAgent);
|
||||||
|
$.info(`指定获取流量的 subInfoUserAgent: ${subInfoUserAgent}`);
|
||||||
|
}
|
||||||
if (content) {
|
if (content) {
|
||||||
content = decodeURIComponent(content);
|
content = decodeURIComponent(content);
|
||||||
$.info(`指定本地文件: ${content}`);
|
$.info(`指定本地文件: ${content}`);
|
||||||
@@ -86,6 +103,26 @@ async function getFile(req, res) {
|
|||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
subInfoUrl = subInfoUrl || file.subInfoUrl;
|
||||||
|
if (subInfoUrl) {
|
||||||
|
// forward flow headers
|
||||||
|
const flowInfo = await getFlowHeaders(
|
||||||
|
subInfoUrl,
|
||||||
|
subInfoUserAgent || file.subInfoUserAgent,
|
||||||
|
);
|
||||||
|
if (flowInfo) {
|
||||||
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`文件 ${name} 获取流量信息时发生错误: ${JSON.stringify(
|
||||||
|
err,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
res.set('Content-Type', 'text/plain; charset=utf-8').send(
|
||||||
output ?? '',
|
output ?? '',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export default function register($app) {
|
|||||||
async function getFlowInfo(req, res) {
|
async function getFlowInfo(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
|
let { url } = req.query;
|
||||||
|
if (url) {
|
||||||
|
url = decodeURIComponent(url);
|
||||||
|
$.info(`指定远程订阅 URL: ${url}`);
|
||||||
|
}
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (!sub) {
|
if (!sub) {
|
||||||
@@ -68,10 +73,11 @@ async function getFlowInfo(req, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let url = `${sub.url}`
|
url =
|
||||||
.split(/[\r\n]+/)
|
`${url || sub.url}`
|
||||||
.map((i) => i.trim())
|
.split(/[\r\n]+/)
|
||||||
.filter((i) => i.length)?.[0];
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0] || '';
|
||||||
|
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -118,6 +124,7 @@ async function getFlowInfo(req, res) {
|
|||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
);
|
);
|
||||||
if (!flowHeaders) {
|
if (!flowHeaders) {
|
||||||
failed(
|
failed(
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ async function produceArtifact({
|
|||||||
produceType,
|
produceType,
|
||||||
produceOpts = {},
|
produceOpts = {},
|
||||||
subscription,
|
subscription,
|
||||||
|
awaitCustomCache,
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
@@ -67,6 +68,8 @@ async function produceArtifact({
|
|||||||
ua || sub.ua,
|
ua || sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
|
undefined,
|
||||||
|
awaitCustomCache,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
@@ -112,6 +115,8 @@ async function produceArtifact({
|
|||||||
ua || sub.ua,
|
ua || sub.ua,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
|
undefined,
|
||||||
|
awaitCustomCache,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
@@ -503,6 +508,7 @@ async function syncArtifacts() {
|
|||||||
await produceArtifact({
|
await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name: subName,
|
name: subName,
|
||||||
|
awaitCustomCache: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// $.error(`${e.message ?? e}`);
|
// $.error(`${e.message ?? e}`);
|
||||||
|
|||||||
49
backend/src/utils/dns.js
Normal file
49
backend/src/utils/dns.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import $ from '@/core/app';
|
||||||
|
import dnsPacket from 'dns-packet';
|
||||||
|
import { Buffer } from 'buffer';
|
||||||
|
|
||||||
|
export async function doh({ url, domain, type = 'A', timeout, edns }) {
|
||||||
|
const buf = dnsPacket.encode({
|
||||||
|
type: 'query',
|
||||||
|
id: 0,
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
name: domain,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
additionals: [
|
||||||
|
{
|
||||||
|
type: 'OPT',
|
||||||
|
name: '.',
|
||||||
|
udpPayloadSize: 4096,
|
||||||
|
flags: 0,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
code: 'CLIENT_SUBNET',
|
||||||
|
ip: edns,
|
||||||
|
sourcePrefixLength: 24,
|
||||||
|
scopePrefixLength: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const res = await $.http.get({
|
||||||
|
url: `${url}?dns=${buf
|
||||||
|
.toString('base64')
|
||||||
|
.toString('utf-8')
|
||||||
|
.replace(/=/g, '')}`,
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/dns-message',
|
||||||
|
// 'Content-Type': 'application/dns-message',
|
||||||
|
},
|
||||||
|
// body: buf,
|
||||||
|
'binary-mode': true,
|
||||||
|
encoding: null, // 使用 null 编码以确保响应是原始二进制数据
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
return dnsPacket.decode(Buffer.from($.env.isQX ? res.bodyBytes : res.body));
|
||||||
|
}
|
||||||
@@ -14,7 +14,14 @@ import $ from '@/core/app';
|
|||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(rawUrl, ua, timeout, proxy) {
|
export default async function download(
|
||||||
|
rawUrl = '',
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
skipCustomCache,
|
||||||
|
awaitCustomCache,
|
||||||
|
) {
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
let url = rawUrl.replace(/#noFlow$/, '');
|
let url = rawUrl.replace(/#noFlow$/, '');
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -35,10 +42,68 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
|
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||||
|
$.read(SETTINGS_KEY);
|
||||||
|
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||||
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
|
const id = hex_md5(userAgent + url);
|
||||||
|
|
||||||
const customCacheKey = $arguments?.cacheKey
|
const customCacheKey = $arguments?.cacheKey
|
||||||
? `#sub-store-cached-custom-${$arguments?.cacheKey}`
|
? `#sub-store-cached-custom-${$arguments?.cacheKey}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
if (customCacheKey && !skipCustomCache) {
|
||||||
|
const customCached = $.read(customCacheKey);
|
||||||
|
const cached = resourceCache.get(id);
|
||||||
|
if (!$arguments?.noCache && cached) {
|
||||||
|
$.info(
|
||||||
|
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
||||||
|
);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
if (customCached) {
|
||||||
|
if (awaitCustomCache) {
|
||||||
|
$.info(`乐观缓存: URL ${url}\n本次进行请求 尝试更新缓存`);
|
||||||
|
try {
|
||||||
|
await download(
|
||||||
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`乐观缓存: URL ${url} 更新缓存发生错误 ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
$.info('使用乐观缓存的数据刷新缓存, 防止后续请求');
|
||||||
|
resourceCache.set(id, customCached);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$.info(
|
||||||
|
`乐观缓存: URL ${url}\n本次返回自定义缓存 ${$arguments?.cacheKey}\n并进行请求 尝试异步更新缓存`,
|
||||||
|
);
|
||||||
|
download(
|
||||||
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
true,
|
||||||
|
).catch((e) => {
|
||||||
|
$.error(
|
||||||
|
`乐观缓存: URL ${url} 异步更新缓存发生错误 ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return customCached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
// if (downloadUrlMatch) {
|
// if (downloadUrlMatch) {
|
||||||
// let type = downloadUrlMatch?.[1];
|
// let type = downloadUrlMatch?.[1];
|
||||||
@@ -56,12 +121,6 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
// return item.content;
|
// return item.content;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
|
||||||
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
|
||||||
$.read(SETTINGS_KEY);
|
|
||||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
|
||||||
const id = hex_md5(userAgent + url);
|
|
||||||
if (!isNode && tasks.has(id)) {
|
if (!isNode && tasks.has(id)) {
|
||||||
return tasks.get(id);
|
return tasks.get(id);
|
||||||
}
|
}
|
||||||
@@ -84,6 +143,10 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
if (!$arguments?.noCache && cached) {
|
if (!$arguments?.noCache && cached) {
|
||||||
$.info(`使用缓存: ${url}`);
|
$.info(`使用缓存: ${url}`);
|
||||||
result = cached;
|
result = cached;
|
||||||
|
if (customCacheKey) {
|
||||||
|
$.info(`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`);
|
||||||
|
$.write(cached, customCacheKey);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$.info(
|
$.info(
|
||||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
||||||
@@ -120,6 +183,9 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
if (shouldCache) {
|
if (shouldCache) {
|
||||||
resourceCache.set(id, body);
|
resourceCache.set(id, body);
|
||||||
if (customCacheKey) {
|
if (customCacheKey) {
|
||||||
|
$.info(
|
||||||
|
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||||
|
);
|
||||||
$.write(body, customCacheKey);
|
$.write(body, customCacheKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +217,7 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
$arguments.flowUserAgent,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
proxy,
|
proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const {
|
|||||||
isShadowRocket,
|
isShadowRocket,
|
||||||
isLanceX,
|
isLanceX,
|
||||||
isEgern,
|
isEgern,
|
||||||
|
isGUIforCores,
|
||||||
} = ENV();
|
} = ENV();
|
||||||
let backend = 'Node';
|
let backend = 'Node';
|
||||||
if (isNode) backend = 'Node';
|
if (isNode) backend = 'Node';
|
||||||
@@ -20,6 +21,7 @@ if (isStash) backend = 'Stash';
|
|||||||
if (isShadowRocket) backend = 'ShadowRocket';
|
if (isShadowRocket) backend = 'ShadowRocket';
|
||||||
if (isEgern) backend = 'Egern';
|
if (isEgern) backend = 'Egern';
|
||||||
if (isLanceX) backend = 'LanceX';
|
if (isLanceX) backend = 'LanceX';
|
||||||
|
if (isGUIforCores) backend = 'GUI.for.Cores';
|
||||||
|
|
||||||
let meta = {};
|
let meta = {};
|
||||||
|
|
||||||
@@ -36,6 +38,10 @@ try {
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
meta.script = $script;
|
meta.script = $script;
|
||||||
}
|
}
|
||||||
|
if (typeof $Plugin !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.plugin = $Plugin;
|
||||||
|
}
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
meta.node = {
|
meta.node = {
|
||||||
version: eval('process.version'),
|
version: eval('process.version'),
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export function getFlowField(headers) {
|
|||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
||||||
let url = rawUrl;
|
let url = flowUrl || rawUrl || '';
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
url = url.split('#')[0];
|
url = url.split('#')[0];
|
||||||
@@ -48,60 +48,76 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
|||||||
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const http = HTTP();
|
const http = HTTP();
|
||||||
try {
|
if (flowUrl) {
|
||||||
$.info(
|
$.info(
|
||||||
`使用 HEAD 方法获取流量信息: ${url}, User-Agent: ${
|
`使用 GET 方法从响应体获取流量信息: ${flowUrl}, User-Agent: ${
|
||||||
userAgent || ''
|
userAgent || ''
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
const { headers } = await http.head({
|
const { body } = await http.get({
|
||||||
url: url
|
url: flowUrl,
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)[0],
|
|
||||||
headers: {
|
|
||||||
'User-Agent': userAgent,
|
|
||||||
...(isStash && proxy
|
|
||||||
? {
|
|
||||||
'X-Stash-Selected-Proxy':
|
|
||||||
encodeURIComponent(proxy),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...(isShadowRocket && proxy
|
|
||||||
? { 'X-Surge-Policy': proxy }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
timeout: requestTimeout,
|
|
||||||
...(proxy ? { proxy } : {}),
|
|
||||||
...(isLoon && proxy ? { node: proxy } : {}),
|
|
||||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
|
||||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
|
||||||
});
|
|
||||||
flowInfo = getFlowField(headers);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`使用 HEAD 方法获取流量信息失败: ${url}, User-Agent: ${
|
|
||||||
userAgent || ''
|
|
||||||
}: ${e.message ?? e}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!flowInfo) {
|
|
||||||
$.info(
|
|
||||||
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
|
||||||
userAgent || ''
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
const { headers } = await http.get({
|
|
||||||
url: url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)[0],
|
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
});
|
});
|
||||||
flowInfo = getFlowField(headers);
|
flowInfo = body;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$.info(
|
||||||
|
`使用 HEAD 方法从响应头获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
const { headers } = await http.head({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`使用 HEAD 方法从响应头获取流量信息失败: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!flowInfo) {
|
||||||
|
$.info(
|
||||||
|
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
const { headers } = await http.get({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
headersResourceCache.set(url, flowInfo);
|
headersResourceCache.set(url, flowInfo);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
const ISOFlags = {
|
const ISOFlags = {
|
||||||
'🏳️🌈': ['EXP', 'BAND'],
|
'🏳️🌈': ['EXP', 'BAND'],
|
||||||
'🇸🇱': ['TEST', 'SOS'],
|
'🇸🇱': ['TEST', 'SOS'],
|
||||||
@@ -327,6 +329,7 @@ export function getFlag(name) {
|
|||||||
'台',
|
'台',
|
||||||
'臺',
|
'臺',
|
||||||
'Taipei',
|
'Taipei',
|
||||||
|
'Tai Wan',
|
||||||
],
|
],
|
||||||
'🇺🇦': ['Ukraine', '乌克兰', '烏克蘭'],
|
'🇺🇦': ['Ukraine', '乌克兰', '烏克蘭'],
|
||||||
'🇺🇸': [
|
'🇺🇸': [
|
||||||
@@ -427,3 +430,48 @@ export function getFlag(name) {
|
|||||||
export function getISO(name) {
|
export function getISO(name) {
|
||||||
return ISOFlags[getFlag(name)]?.[0];
|
return ISOFlags[getFlag(name)]?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove flag
|
||||||
|
export function removeFlag(str) {
|
||||||
|
return str
|
||||||
|
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]|🏴☠️|🏳️🌈/g, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MMDB {
|
||||||
|
constructor({ country, asn } = {}) {
|
||||||
|
if ($.env.isNode) {
|
||||||
|
const Reader = eval(`require("@maxmind/geoip2-node")`).Reader;
|
||||||
|
const fs = eval("require('fs')");
|
||||||
|
const countryFile =
|
||||||
|
country || eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH');
|
||||||
|
const asnFile = asn || eval('process.env.SUB_STORE_MMDB_ASN_PATH');
|
||||||
|
// $.info(
|
||||||
|
// `GeoLite2 Country MMDB: ${countryFile}, exists: ${fs.existsSync(
|
||||||
|
// countryFile,
|
||||||
|
// )}`,
|
||||||
|
// );
|
||||||
|
if (countryFile) {
|
||||||
|
this.countryReader = Reader.openBuffer(
|
||||||
|
fs.readFileSync(countryFile),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// $.info(
|
||||||
|
// `GeoLite2 ASN MMDB: ${asnFile}, exists: ${fs.existsSync(
|
||||||
|
// asnFile,
|
||||||
|
// )}`,
|
||||||
|
// );
|
||||||
|
if (asnFile) {
|
||||||
|
if (!fs.existsSync(asnFile))
|
||||||
|
throw new Error('GeoLite2 ASN MMDB does not exist');
|
||||||
|
this.asnReader = Reader.openBuffer(fs.readFileSync(asnFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoip(ip) {
|
||||||
|
return this.countryReader?.country(ip)?.country?.isoCode;
|
||||||
|
}
|
||||||
|
ipaso(ip) {
|
||||||
|
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as ipAddress from 'ip-address';
|
||||||
// source: https://stackoverflow.com/a/36760050
|
// source: https://stackoverflow.com/a/36760050
|
||||||
const IPV4_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
const IPV4_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
||||||
|
|
||||||
@@ -46,54 +47,55 @@ function getPolicyDescriptor(str) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const utf8ArrayToStr =
|
// const utf8ArrayToStr =
|
||||||
typeof TextDecoder !== 'undefined'
|
// typeof TextDecoder !== 'undefined'
|
||||||
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
// ? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||||
: (function () {
|
// : (function () {
|
||||||
var charCache = new Array(128); // Preallocate the cache for the common single byte chars
|
// var charCache = new Array(128); // Preallocate the cache for the common single byte chars
|
||||||
var charFromCodePt = String.fromCodePoint || String.fromCharCode;
|
// var charFromCodePt = String.fromCodePoint || String.fromCharCode;
|
||||||
var result = [];
|
// var result = [];
|
||||||
|
|
||||||
return function (array) {
|
// return function (array) {
|
||||||
var codePt, byte1;
|
// var codePt, byte1;
|
||||||
var buffLen = array.length;
|
// var buffLen = array.length;
|
||||||
|
|
||||||
result.length = 0;
|
// result.length = 0;
|
||||||
|
|
||||||
for (var i = 0; i < buffLen; ) {
|
// for (var i = 0; i < buffLen; ) {
|
||||||
byte1 = array[i++];
|
// byte1 = array[i++];
|
||||||
|
|
||||||
if (byte1 <= 0x7f) {
|
// if (byte1 <= 0x7f) {
|
||||||
codePt = byte1;
|
// codePt = byte1;
|
||||||
} else if (byte1 <= 0xdf) {
|
// } else if (byte1 <= 0xdf) {
|
||||||
codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
|
// codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
|
||||||
} else if (byte1 <= 0xef) {
|
// } else if (byte1 <= 0xef) {
|
||||||
codePt =
|
// codePt =
|
||||||
((byte1 & 0x0f) << 12) |
|
// ((byte1 & 0x0f) << 12) |
|
||||||
((array[i++] & 0x3f) << 6) |
|
// ((array[i++] & 0x3f) << 6) |
|
||||||
(array[i++] & 0x3f);
|
// (array[i++] & 0x3f);
|
||||||
} else if (String.fromCodePoint) {
|
// } else if (String.fromCodePoint) {
|
||||||
codePt =
|
// codePt =
|
||||||
((byte1 & 0x07) << 18) |
|
// ((byte1 & 0x07) << 18) |
|
||||||
((array[i++] & 0x3f) << 12) |
|
// ((array[i++] & 0x3f) << 12) |
|
||||||
((array[i++] & 0x3f) << 6) |
|
// ((array[i++] & 0x3f) << 6) |
|
||||||
(array[i++] & 0x3f);
|
// (array[i++] & 0x3f);
|
||||||
} else {
|
// } else {
|
||||||
codePt = 63; // Cannot convert four byte code points, so use "?" instead
|
// codePt = 63; // Cannot convert four byte code points, so use "?" instead
|
||||||
i += 3;
|
// i += 3;
|
||||||
}
|
// }
|
||||||
|
|
||||||
result.push(
|
// result.push(
|
||||||
charCache[codePt] ||
|
// charCache[codePt] ||
|
||||||
(charCache[codePt] = charFromCodePt(codePt)),
|
// (charCache[codePt] = charFromCodePt(codePt)),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
return result.join('');
|
// return result.join('');
|
||||||
};
|
// };
|
||||||
})();
|
// })();
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
isValidPortNumber,
|
isValidPortNumber,
|
||||||
@@ -101,6 +103,6 @@ export {
|
|||||||
getIfNotBlank,
|
getIfNotBlank,
|
||||||
isPresent,
|
isPresent,
|
||||||
getIfPresent,
|
getIfPresent,
|
||||||
utf8ArrayToStr,
|
// utf8ArrayToStr,
|
||||||
getPolicyDescriptor,
|
getPolicyDescriptor,
|
||||||
};
|
};
|
||||||
|
|||||||
11
backend/src/utils/rs.js
Normal file
11
backend/src/utils/rs.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import rs from 'jsrsasign';
|
||||||
|
|
||||||
|
export function generateFingerprint(caStr) {
|
||||||
|
const hex = rs.pemtohex(caStr);
|
||||||
|
const fingerPrint = rs.KJUR.crypto.Util.hashHex(hex, 'sha256');
|
||||||
|
return fingerPrint.match(/.{2}/g).join(':').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
generateFingerprint,
|
||||||
|
};
|
||||||
4
backend/src/vendor/express.js
vendored
4
backend/src/vendor/express.js
vendored
@@ -161,7 +161,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
|
|
||||||
function Response() {
|
function Response() {
|
||||||
let statusCode = 200;
|
let statusCode = 200;
|
||||||
const { isQX, isLoon, isSurge } = ENV();
|
const { isQX, isLoon, isSurge, isGUIforCores } = ENV();
|
||||||
const headers = DEFAULT_HEADERS;
|
const headers = DEFAULT_HEADERS;
|
||||||
const STATUS_CODE_MAP = {
|
const STATUS_CODE_MAP = {
|
||||||
200: 'HTTP/1.1 200 OK',
|
200: 'HTTP/1.1 200 OK',
|
||||||
@@ -184,7 +184,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
body,
|
body,
|
||||||
headers,
|
headers,
|
||||||
};
|
};
|
||||||
if (isQX) {
|
if (isQX || isGUIforCores) {
|
||||||
$done(response);
|
$done(response);
|
||||||
} else if (isLoon || isSurge) {
|
} else if (isLoon || isSurge) {
|
||||||
$done({
|
$done({
|
||||||
|
|||||||
51
backend/src/vendor/open-api.js
vendored
51
backend/src/vendor/open-api.js
vendored
@@ -8,6 +8,7 @@ const isStash =
|
|||||||
const isShadowRocket = 'undefined' !== typeof $rocket;
|
const isShadowRocket = 'undefined' !== typeof $rocket;
|
||||||
const isEgern = 'object' == typeof egern;
|
const isEgern = 'object' == typeof egern;
|
||||||
const isLanceX = 'undefined' != typeof $native;
|
const isLanceX = 'undefined' != typeof $native;
|
||||||
|
const isGUIforCores = typeof $Plugins !== 'undefined';
|
||||||
|
|
||||||
export class OpenAPI {
|
export class OpenAPI {
|
||||||
constructor(name = 'untitled', debug = false) {
|
constructor(name = 'untitled', debug = false) {
|
||||||
@@ -48,7 +49,10 @@ export class OpenAPI {
|
|||||||
this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}');
|
this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}');
|
||||||
if (isLoon || isSurge)
|
if (isLoon || isSurge)
|
||||||
this.cache = JSON.parse($persistentStore.read(this.name) || '{}');
|
this.cache = JSON.parse($persistentStore.read(this.name) || '{}');
|
||||||
|
if (isGUIforCores)
|
||||||
|
this.cache = JSON.parse(
|
||||||
|
$Plugins.SubStoreCache.get(this.name) || '{}',
|
||||||
|
);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
// create a json for root cache
|
// create a json for root cache
|
||||||
const basePath =
|
const basePath =
|
||||||
@@ -86,6 +90,7 @@ export class OpenAPI {
|
|||||||
const data = JSON.stringify(this.cache, null, 2);
|
const data = JSON.stringify(this.cache, null, 2);
|
||||||
if (isQX) $prefs.setValueForKey(data, this.name);
|
if (isQX) $prefs.setValueForKey(data, this.name);
|
||||||
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
||||||
|
if (isGUIforCores) $Plugins.SubStoreCache.set(this.name, data);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const basePath =
|
const basePath =
|
||||||
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
@@ -118,6 +123,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
this.root[key] = data;
|
this.root[key] = data;
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.set(key, data);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.cache[key] = data;
|
this.cache[key] = data;
|
||||||
}
|
}
|
||||||
@@ -137,6 +145,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
return this.root[key];
|
return this.root[key];
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.get(key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.cache[key];
|
return this.cache[key];
|
||||||
}
|
}
|
||||||
@@ -155,6 +166,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
delete this.root[key];
|
delete this.root[key];
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.remove(key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete this.cache[key];
|
delete this.cache[key];
|
||||||
}
|
}
|
||||||
@@ -220,6 +234,9 @@ export class OpenAPI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
$Plugins.Notify(title, subtitle + '\n' + content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// other helper functions
|
// other helper functions
|
||||||
@@ -240,7 +257,7 @@ export class OpenAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
done(value = {}) {
|
done(value = {}) {
|
||||||
if (isQX || isLoon || isSurge) {
|
if (isQX || isLoon || isSurge || isGUIforCores) {
|
||||||
$done(value);
|
$done(value);
|
||||||
} else if (isNode) {
|
} else if (isNode) {
|
||||||
if (typeof $context !== 'undefined') {
|
if (typeof $context !== 'undefined') {
|
||||||
@@ -262,11 +279,12 @@ export function ENV() {
|
|||||||
isShadowRocket,
|
isShadowRocket,
|
||||||
isEgern,
|
isEgern,
|
||||||
isLanceX,
|
isLanceX,
|
||||||
|
isGUIforCores,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HTTP(defaultOptions = { baseURL: '' }) {
|
export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||||
const { isQX, isLoon, isSurge, isNode } = ENV();
|
const { isQX, isLoon, isSurge, isNode, isGUIforCores } = ENV();
|
||||||
const methods = [
|
const methods = [
|
||||||
'GET',
|
'GET',
|
||||||
'POST',
|
'POST',
|
||||||
@@ -323,7 +341,10 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
const request = isNode
|
const request = isNode
|
||||||
? eval("require('request')")
|
? eval("require('request')")
|
||||||
: $httpClient;
|
: $httpClient;
|
||||||
|
const body = options.body;
|
||||||
const opts = JSON.parse(JSON.stringify(options));
|
const opts = JSON.parse(JSON.stringify(options));
|
||||||
|
opts.body = body;
|
||||||
|
|
||||||
if (!isNode && opts.timeout) {
|
if (!isNode && opts.timeout) {
|
||||||
opts.timeout++;
|
opts.timeout++;
|
||||||
let unit = 'ms';
|
let unit = 'ms';
|
||||||
@@ -356,6 +377,30 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (isGUIforCores) {
|
||||||
|
worker = new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const response = await $Plugins.Requests({
|
||||||
|
method,
|
||||||
|
url: options.url,
|
||||||
|
headers: options.headers,
|
||||||
|
body: options.body,
|
||||||
|
options: {
|
||||||
|
Proxy: options.proxy,
|
||||||
|
Timeout: options.timeout
|
||||||
|
? options.timeout / 1000
|
||||||
|
: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve({
|
||||||
|
statusCode: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
body: response.body,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutid;
|
let timeoutid;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具. 定时任务默认为每天 23 点 55 分
|
#!desc=高级订阅管理工具. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!openUrl=https://sub.store
|
#!openUrl=https://sub.store
|
||||||
#!author=Peng-YM
|
#!author=Peng-YM
|
||||||
#!homepage=https://github.com/sub-store-org/Sub-Store
|
#!homepage=https://github.com/sub-store-org/Sub-Store
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Sub-Store",
|
"name": "Sub-Store",
|
||||||
"description": "定时任务默认为每天 23 点 55 分",
|
"description": "定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'",
|
||||||
"task": [
|
"task": [
|
||||||
"55 23 * * * https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"
|
"55 23 * * * https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,17 +6,29 @@ Sub-Store Releases: [`https://github.com/sub-store-org/Sub-Store/releases`](http
|
|||||||
|
|
||||||
Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
||||||
|
|
||||||
## 脚本配置:
|
## 服务器/云平台/Docker/Android 版
|
||||||
|
|
||||||
|
https://xream.notion.site/Sub-Store-abe6a96944724dc6a36833d5c9ab7c87
|
||||||
|
|
||||||
|
## App 版
|
||||||
|
|
||||||
### 1. Loon
|
### 1. Loon
|
||||||
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
|
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
|
||||||
|
|
||||||
### 2. Surge
|
### 2. Surge
|
||||||
|
|
||||||
|
#### 关于 Surge 的格外说明
|
||||||
|
|
||||||
|
Surge Mac 版如何支持 SSR, 如何去除 HTTP 传输层以支持 类似 VMess HTTP 节点等 请查看 [链接参数说明](https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E)
|
||||||
|
|
||||||
|
定时处理订阅 功能, 避免 App 内拉取超时, 请查看 [定时处理订阅](https://t.me/zhetengsha/1449)
|
||||||
|
|
||||||
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的特性): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的特性): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
||||||
|
|
||||||
1. 官方默认版模块(支持 App 内使用编辑参数): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
1. 官方默认版模块(支持 App 内使用编辑参数): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
||||||
|
|
||||||
|
> 最新版 Surge 已删除 `ability: http-client-policy` 参数, 模块暂不做修改, 对测落地功能无影响
|
||||||
|
|
||||||
2. 经典版, 不支持编辑参数, 固定带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
|
2. 经典版, 不支持编辑参数, 固定带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
|
||||||
|
|
||||||
3. 经典版, 不支持编辑参数, 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule)
|
3. 经典版, 不支持编辑参数, 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule)
|
||||||
@@ -31,9 +43,17 @@ Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
|||||||
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
|
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
|
||||||
|
|
||||||
### 5. Shadowrocket
|
### 5. Shadowrocket
|
||||||
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule) 即可。
|
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 即可。
|
||||||
|
|
||||||
## 使用 Sub-Store
|
## 使用 Sub-Store
|
||||||
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
||||||
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
|
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
|
||||||
3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。
|
3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。
|
||||||
|
|
||||||
|
## 链接参数说明
|
||||||
|
|
||||||
|
https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
||||||
|
|
||||||
|
## 脚本使用说明
|
||||||
|
|
||||||
|
https://github.com/sub-store-org/Sub-Store/wiki/%E8%84%9A%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Sub-Store
|
name: Sub-Store
|
||||||
desc: 高级订阅管理工具 @Peng-YM. 定时任务默认为每天 23 点 55 分
|
desc: 高级订阅管理工具 @Peng-YM. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
icon: https://raw.githubusercontent.com/cc63/ICON/main/Sub-Store.png
|
icon: https://raw.githubusercontent.com/cc63/ICON/main/Sub-Store.png
|
||||||
|
|
||||||
http:
|
http:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!name=Sub-Store(β)
|
#!name=Sub-Store(β)
|
||||||
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto,produce:"# Sub-Store Produce",produce_cronexp:50 */6 * * *,produce_sub:"sub1,sub2",produce_col:"col1,col2"
|
||||||
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存
|
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n脚本超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6️⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7️⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9️⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠️ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname = %APPEND% sub.store
|
hostname = %APPEND% sub.store
|
||||||
@@ -12,4 +12,6 @@ Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api
|
|||||||
|
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
||||||
|
|
||||||
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
||||||
|
|
||||||
|
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 23 点 55 分
|
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 23 点 55 分
|
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto,produce:"# Sub-Store Produce",produce_cronexp:50 */6 * * *,produce_sub:"sub1,sub2",produce_col:"col1,col2"
|
||||||
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存
|
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n脚本超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6️⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7️⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9️⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠️ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname = %APPEND% sub.store
|
hostname = %APPEND% sub.store
|
||||||
@@ -12,4 +12,6 @@ Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api
|
|||||||
|
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
||||||
|
|
||||||
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
||||||
|
|
||||||
|
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
function operator(proxies = [], targetPlatform, context) {
|
function operator(proxies = [], targetPlatform, context) {
|
||||||
// 支持快捷操作 不一定要写一个 function
|
// 支持快捷操作 不一定要写一个 function
|
||||||
// 可参考 https://t.me/zhetengsha/970
|
// 可参考 https://t.me/zhetengsha/970
|
||||||
// https://t.me/zhetengsha/1009
|
// https://t.me/zhetengsha/1009
|
||||||
|
|
||||||
|
|
||||||
// proxies 为传入的内部节点数组
|
// proxies 为传入的内部节点数组
|
||||||
// 结构大致参考了 Clash.Meta(mihomo) 有私货
|
|
||||||
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
||||||
// 1. `no-resolve` 为不解析域名
|
// 0. 结构大致参考了 Clash.Meta(mihomo), 可参考 mihomo 的文档, 例如 `xudp`, `smux` 都可以自己设置. 但是有私货, 下面是我能想起来的一些私货
|
||||||
// 2. 域名解析后 会多一个 `resolved` 字段
|
// 1. `_no-resolve` 为不解析域名
|
||||||
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_domain` 字段
|
// 2. 域名解析后 会多一个 `_resolved` 字段, 表示是否解析成功
|
||||||
|
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_IP4P`(若解析类型为 IPv6 且符合 IP4P 类型, 将自动转换), `_domain` 字段, `_resolved_ips` 为解析出的所有 IP
|
||||||
// 4. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
// 4. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
||||||
|
// 5. `_subName` 为单条订阅名
|
||||||
|
// 6. `_collectionName` 为组合订阅名
|
||||||
|
// 7. `tls-fingerprint` 为 tls 指纹
|
||||||
|
// 8. `underlying-proxy` 为前置代理
|
||||||
|
// 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`
|
||||||
|
//
|
||||||
|
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@@ -20,7 +29,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// $substore 为 OpenAPI
|
// $substore 为 OpenAPI
|
||||||
// 参考 https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
|
// 参考 https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
|
||||||
|
|
||||||
// scriptResourceCache 缓存
|
// scriptResourceCache 缓存
|
||||||
// 可参考 https://t.me/zhetengsha/1003
|
// 可参考 https://t.me/zhetengsha/1003
|
||||||
// const cache = scriptResourceCache
|
// const cache = scriptResourceCache
|
||||||
@@ -33,17 +42,21 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// parse, // 订阅解析
|
// parse, // 订阅解析
|
||||||
// process, // 节点操作/文件操作
|
// process, // 节点操作/文件操作
|
||||||
// produce, // 输出订阅
|
// produce, // 输出订阅
|
||||||
|
// ipAddress, // https://github.com/beaugunderson/ip-address
|
||||||
// isIPv4,
|
// isIPv4,
|
||||||
// isIPv6,
|
// isIPv6,
|
||||||
// isIP,
|
// isIP,
|
||||||
// yaml, // yaml 解析和生成
|
// yaml, // yaml 解析和生成
|
||||||
// getFlag, // 获取 emoji 旗帜
|
// getFlag, // 获取 emoji 旗帜
|
||||||
|
// removeFlag, // 移除 emoji 旗帜
|
||||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||||
// Gist, // Gist 类
|
// Gist, // Gist 类
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 示例: 给节点名添加前缀
|
// 示例: 给节点名添加前缀
|
||||||
// $server.name = `[${ProxyUtils.getISO($server.name)}] ${$server.name}`
|
// $server.name = `[${ProxyUtils.getISO($server.name)}] ${$server.name}`
|
||||||
|
// 示例: 给节点名添加旗帜
|
||||||
|
// $server.name = `[${ProxyUtils.getFlag($server.name).replace(/🇹🇼/g, '🇼🇸')}] ${ProxyUtils.removeFlag($server.name)}`
|
||||||
|
|
||||||
// 示例: 从 sni 文件中读取内容并进行节点操作
|
// 示例: 从 sni 文件中读取内容并进行节点操作
|
||||||
// const sni = await produceArtifact({
|
// const sni = await produceArtifact({
|
||||||
@@ -63,7 +76,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// $content = proxies
|
// $content = proxies
|
||||||
|
|
||||||
// 2. sing-box
|
// 2. sing-box
|
||||||
|
|
||||||
// 但是一般不需要这样用, 可参考
|
// 但是一般不需要这样用, 可参考
|
||||||
@@ -96,40 +109,9 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// })
|
// })
|
||||||
|
|
||||||
// 4. 一个比较折腾的方案: 在脚本操作中, 把内容同步到另一个 gist
|
// 4. 一个比较折腾的方案: 在脚本操作中, 把内容同步到另一个 gist
|
||||||
|
// 见 https://t.me/zhetengsha/1428
|
||||||
// async function operator(proxies = []) {
|
//
|
||||||
// const $ = $substore
|
// const content = ProxyUtils.produce([...proxies], platform)
|
||||||
// const GITHUB_TOKEN = 'ghp_xxxxxxxxxxxxxxxxxxxxx'
|
|
||||||
// const GIST_NAME = 'share'
|
|
||||||
// const FILENAME = 'mihomo.yaml'
|
|
||||||
// let files = {}
|
|
||||||
// let content = await produceArtifact({
|
|
||||||
// type: 'subscription',
|
|
||||||
// subscription: {},
|
|
||||||
// content: 'proxies:\n' + proxies.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n').join(''),
|
|
||||||
// platform: 'ClashMeta',
|
|
||||||
// })
|
|
||||||
// const manager = new ProxyUtils.Gist({
|
|
||||||
// token: GITHUB_TOKEN,
|
|
||||||
// key: GIST_NAME,
|
|
||||||
// });
|
|
||||||
// files[encodeURIComponent(FILENAME)] = {
|
|
||||||
// content,
|
|
||||||
// };
|
|
||||||
// const res = await manager.upload(files);
|
|
||||||
// let body = {};
|
|
||||||
// try {
|
|
||||||
// body = JSON.parse(res.body);
|
|
||||||
// // eslint-disable-next-line no-empty
|
|
||||||
// } catch (e) {}
|
|
||||||
// const raw_url =
|
|
||||||
// body.files[encodeURIComponent(FILENAME)]?.raw_url;
|
|
||||||
// console.log(raw_url)
|
|
||||||
// const new_url = raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
|
||||||
// console.log(new_url)
|
|
||||||
// $.notify('🌍 Sub-Store', `更新到 Gist: ${new_url}`);
|
|
||||||
// return proxies
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // YAML
|
// // YAML
|
||||||
// ProxyUtils.yaml.load('YAML String')
|
// ProxyUtils.yaml.load('YAML String')
|
||||||
@@ -148,12 +130,11 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// yaml.proxies.unshift(...clashMetaProxies)
|
// yaml.proxies.unshift(...clashMetaProxies)
|
||||||
// $content = ProxyUtils.yaml.dump(yaml)
|
// $content = ProxyUtils.yaml.dump(yaml)
|
||||||
|
|
||||||
|
// { $content, $files } will be passed to the next operator
|
||||||
// { $content, $files } will be passed to the next operator
|
|
||||||
// $content is the final content of the file
|
// $content is the final content of the file
|
||||||
|
|
||||||
// flowUtils 为机场订阅流量信息处理工具
|
// flowUtils 为机场订阅流量信息处理工具
|
||||||
// 可参考:
|
// 可参考:
|
||||||
// 1. https://t.me/zhetengsha/948
|
// 1. https://t.me/zhetengsha/948
|
||||||
|
|
||||||
// context 为传入的上下文
|
// context 为传入的上下文
|
||||||
@@ -242,7 +223,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 参数说明
|
// 参数说明
|
||||||
// 可参考 https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
// 可参考 https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
||||||
|
|
||||||
console.log(JSON.stringify(context, null, 2))
|
console.log(JSON.stringify(context, null, 2));
|
||||||
|
|
||||||
return proxies
|
return proxies;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user