mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85a3e2ee54 | ||
|
|
95b7557635 | ||
|
|
14ca62db4a | ||
|
|
a2a754adb7 | ||
|
|
6b23f82953 | ||
|
|
e071a7f253 | ||
|
|
b9bba895e1 | ||
|
|
8090d678ee | ||
|
|
ff4be7ac38 | ||
|
|
7e2109dc68 | ||
|
|
278beae99a | ||
|
|
3aedd5943d | ||
|
|
222551eb20 | ||
|
|
0d5e1ab38b | ||
|
|
a3ec98caa9 | ||
|
|
d9e4d814bb | ||
|
|
e843aa3702 | ||
|
|
66464645f2 | ||
|
|
9ccd6b3816 | ||
|
|
74be1e3d82 | ||
|
|
6d78eb7356 | ||
|
|
38eccca8b4 | ||
|
|
33e5aeceb5 | ||
|
|
837667edc9 | ||
|
|
0069b0ce83 | ||
|
|
fcc9d047ae | ||
|
|
382d22e622 | ||
|
|
06f3e97af2 | ||
|
|
bd87e9231e | ||
|
|
d1d6d19542 | ||
|
|
08bf0b78bb | ||
|
|
9a3cd4f57c | ||
|
|
d015c7867e | ||
|
|
4713b63083 | ||
|
|
dbf9e7c360 | ||
|
|
4ea84118c4 | ||
|
|
dda8113a42 | ||
|
|
f16b2d34f1 | ||
|
|
5b28e1a4c9 | ||
|
|
8d0a71d983 | ||
|
|
815552d470 | ||
|
|
9d90369594 | ||
|
|
6aece471aa | ||
|
|
99396773f6 | ||
|
|
e229408a2d | ||
|
|
514414587b | ||
|
|
d4c419745e | ||
|
|
fe3da254f4 | ||
|
|
7d8132d7cd | ||
|
|
bc1247efaf | ||
|
|
dea937df66 | ||
|
|
cfb5a8e082 | ||
|
|
4790bf47d1 | ||
|
|
56fd495fb6 | ||
|
|
f4639d9a34 | ||
|
|
cc58a5541e | ||
|
|
772f431887 | ||
|
|
2b60c515cd | ||
|
|
c8c22c3901 | ||
|
|
d8f9466b84 | ||
|
|
d12ccad382 |
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@@ -76,8 +76,8 @@ jobs:
|
||||
git commit -m "release: ${{ steps.tag.outputs.release_tag }}"
|
||||
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
|
||||
git push -f -u origin release
|
||||
- name: Sync to GitLab
|
||||
env:
|
||||
GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}
|
||||
run: |
|
||||
curl -X POST --fail -F token=$GITLAB_PIPELINE_TOKEN -F ref=master https://gitlab.com/api/v4/projects/48891296/trigger/pipeline
|
||||
# - name: Sync to GitLab
|
||||
# env:
|
||||
# GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}
|
||||
# run: |
|
||||
# curl -X POST --fail -F token=$GITLAB_PIPELINE_TOKEN -F ref=master https://gitlab.com/api/v4/projects/48891296/trigger/pipeline
|
||||
|
||||
11
README.md
11
README.md
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<p align="center" color="#6a737d">
|
||||
Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.
|
||||
Advanced Subscription Manager for QX, Loon, Surge, Stash, Egern and Shadowrocket.
|
||||
</p>
|
||||
|
||||
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
||||
@@ -28,14 +28,18 @@ Core functionalities:
|
||||
|
||||
> ⚠️ Do not use `Shadowrocket` to export URI and then import it as input. It is not a standard URI.
|
||||
|
||||
- [x] Normal Proxy(`socks5`, `socks5+tls`, `http`, `https`(it's ok))
|
||||
|
||||
example: `socks5+tls://user:pass@ip:port#name`
|
||||
|
||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] Clash Proxies YAML
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||
- [x] Surge (Direct, SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru)
|
||||
- [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)
|
||||
|
||||
@@ -49,6 +53,7 @@ Core functionalities:
|
||||
- [x] Surge
|
||||
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
||||
- [x] Loon
|
||||
- [x] Egern
|
||||
- [x] Shadowrocket
|
||||
- [x] QX
|
||||
- [x] sing-box
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.409",
|
||||
"version": "2.16.3",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -28,13 +28,12 @@
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"ip-address": "^9.0.5",
|
||||
"js-base64": "^3.7.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jsrsasign": "^11.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"ms": "^2.1.3",
|
||||
"nanoid": "^3.3.3",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.3.7",
|
||||
"static-js-yaml": "^1.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
"static-js-yaml": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.0",
|
||||
|
||||
171
backend/pnpm-lock.yaml
generated
171
backend/pnpm-lock.yaml
generated
@@ -41,27 +41,24 @@ importers:
|
||||
js-base64:
|
||||
specifier: ^3.7.2
|
||||
version: https://registry.npmmirror.com/js-base64/-/js-base64-3.7.2.tgz
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
jsrsasign:
|
||||
specifier: ^11.1.0
|
||||
version: https://registry.npmmirror.com/jsrsasign/-/jsrsasign-11.1.0.tgz
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz
|
||||
ms:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
nanoid:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
request:
|
||||
specifier: ^2.88.2
|
||||
version: https://registry.npmmirror.com/request/-/request-2.88.2.tgz
|
||||
semver:
|
||||
specifier: ^7.3.7
|
||||
version: https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz
|
||||
static-js-yaml:
|
||||
specifier: ^1.0.0
|
||||
version: https://registry.npmmirror.com/static-js-yaml/-/static-js-yaml-1.0.0.tgz
|
||||
uuid:
|
||||
specifier: ^8.3.2
|
||||
version: https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.18.0
|
||||
@@ -1509,9 +1506,6 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==, tarball: https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz}
|
||||
|
||||
buffer-equal@https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz:
|
||||
resolution: {integrity: sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==, tarball: https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz}
|
||||
version: 1.0.0
|
||||
@@ -2091,9 +2085,6 @@ packages:
|
||||
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==, tarball: https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz}
|
||||
version: 0.1.2
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==, tarball: https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz}
|
||||
|
||||
ee-first@https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, tarball: https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz}
|
||||
version: 1.1.1
|
||||
@@ -3391,10 +3382,6 @@ packages:
|
||||
version: 1.3.1
|
||||
engines: {'0': node >= 0.2.0}
|
||||
|
||||
jsonwebtoken@9.0.2:
|
||||
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==, tarball: https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
|
||||
jsprim@https://registry.npmmirror.com/jsprim/-/jsprim-1.4.1.tgz:
|
||||
resolution: {integrity: sha512-4Dj8Rf+fQ+/Pn7C5qeEX02op1WfOss3PKTE9Nsop3Dx+6UPxlm1dr/og7o2cRa5hNN07CACr4NFzRLtj/rjWog==, tarball: https://registry.npmmirror.com/jsprim/-/jsprim-1.4.1.tgz}
|
||||
version: 1.4.1
|
||||
@@ -3413,12 +3400,6 @@ packages:
|
||||
resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, tarball: https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz}
|
||||
version: 1.1.0
|
||||
|
||||
jwa@1.4.1:
|
||||
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==, tarball: https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz}
|
||||
|
||||
jws@3.2.2:
|
||||
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==, tarball: https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz}
|
||||
|
||||
keyv@https://registry.npmmirror.com/keyv/-/keyv-3.1.0.tgz:
|
||||
resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==, tarball: https://registry.npmmirror.com/keyv/-/keyv-3.1.0.tgz}
|
||||
version: 3.1.0
|
||||
@@ -3510,24 +3491,6 @@ packages:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, tarball: https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz}
|
||||
version: 4.0.8
|
||||
|
||||
lodash.includes@4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==, tarball: https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz}
|
||||
|
||||
lodash.isboolean@3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==, tarball: https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz}
|
||||
|
||||
lodash.isinteger@4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==, tarball: https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz}
|
||||
|
||||
lodash.isnumber@3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==, tarball: https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz}
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, tarball: https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz}
|
||||
|
||||
lodash.isstring@4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==, tarball: https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz}
|
||||
|
||||
lodash.memoize@https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz:
|
||||
resolution: {integrity: sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==, tarball: https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz}
|
||||
version: 3.0.4
|
||||
@@ -3536,9 +3499,6 @@ packages:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, tarball: https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz}
|
||||
version: 4.6.2
|
||||
|
||||
lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==, tarball: https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz}
|
||||
|
||||
lodash.template@https://registry.npmmirror.com/lodash.template/-/lodash.template-4.5.0.tgz:
|
||||
resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==, tarball: https://registry.npmmirror.com/lodash.template/-/lodash.template-4.5.0.tgz}
|
||||
version: 4.5.0
|
||||
@@ -3741,25 +3701,18 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
hasBin: true
|
||||
|
||||
ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, tarball: https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz}
|
||||
|
||||
ms@2.1.1:
|
||||
resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz}
|
||||
|
||||
ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz}
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, tarball: https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz}
|
||||
version: 2.0.0
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz:
|
||||
resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz}
|
||||
version: 2.1.1
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz}
|
||||
version: 2.1.2
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz}
|
||||
version: 2.1.3
|
||||
|
||||
multi-stage-sourcemap@https://registry.npmmirror.com/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz:
|
||||
resolution: {integrity: sha512-umaOM+8BZByZIB/ciD3dQLzTv50rEkkGJV78ta/tIVc/J/rfGZY5y1R+fBD3oTaolx41mK8rRcyGtYbDXlzx8Q==, tarball: https://registry.npmmirror.com/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz}
|
||||
version: 0.2.1
|
||||
@@ -3785,9 +3738,8 @@ packages:
|
||||
version: 2.1.1
|
||||
hasBin: true
|
||||
|
||||
nanoid@https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz:
|
||||
nanoid@3.3.3:
|
||||
resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz}
|
||||
version: 3.3.3
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -4511,9 +4463,6 @@ packages:
|
||||
resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==, tarball: https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz}
|
||||
version: 2.0.2
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, tarball: https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz}
|
||||
|
||||
safe-buffer@https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, tarball: https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz}
|
||||
version: 5.1.2
|
||||
@@ -4552,11 +4501,6 @@ packages:
|
||||
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==, tarball: https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz}
|
||||
hasBin: true
|
||||
|
||||
semver@7.6.3:
|
||||
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==, tarball: https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz:
|
||||
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==, tarball: https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz}
|
||||
version: 5.7.1
|
||||
@@ -5235,11 +5179,6 @@ packages:
|
||||
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
|
||||
hasBin: true
|
||||
|
||||
uuid@https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, tarball: https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz}
|
||||
version: 8.3.2
|
||||
hasBin: true
|
||||
|
||||
v8-compile-cache@https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz:
|
||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==, tarball: https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz}
|
||||
version: 2.3.0
|
||||
@@ -6824,8 +6763,6 @@ snapshots:
|
||||
node-releases: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.4.tgz
|
||||
picocolors: https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-equal@https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz: {}
|
||||
|
||||
buffer-from@https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz: {}
|
||||
@@ -7202,17 +7139,17 @@ snapshots:
|
||||
|
||||
debug@https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz:
|
||||
dependencies:
|
||||
ms: https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz
|
||||
ms: 2.0.0
|
||||
|
||||
debug@https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz(supports-color@https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz):
|
||||
dependencies:
|
||||
ms: https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz
|
||||
|
||||
debug@https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz(supports-color@https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz):
|
||||
dependencies:
|
||||
ms: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz
|
||||
ms: 2.1.2
|
||||
optionalDependencies:
|
||||
supports-color: https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz
|
||||
|
||||
@@ -7357,10 +7294,6 @@ snapshots:
|
||||
jsbn: https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz
|
||||
safer-buffer: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
ee-first@https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz: {}
|
||||
|
||||
electron-to-chromium@https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz: {}
|
||||
@@ -8561,19 +8494,6 @@ snapshots:
|
||||
|
||||
jsonparse@https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz: {}
|
||||
|
||||
jsonwebtoken@9.0.2:
|
||||
dependencies:
|
||||
jws: 3.2.2
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
ms: 2.1.3
|
||||
semver: 7.6.3
|
||||
|
||||
jsprim@https://registry.npmmirror.com/jsprim/-/jsprim-1.4.1.tgz:
|
||||
dependencies:
|
||||
assert-plus: https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz
|
||||
@@ -8592,17 +8512,6 @@ snapshots:
|
||||
|
||||
just-debounce@https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz: {}
|
||||
|
||||
jwa@1.4.1:
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
jws@3.2.2:
|
||||
dependencies:
|
||||
jwa: 1.4.1
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
keyv@https://registry.npmmirror.com/keyv/-/keyv-3.1.0.tgz:
|
||||
dependencies:
|
||||
json-buffer: https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.0.tgz
|
||||
@@ -8689,24 +8598,10 @@ snapshots:
|
||||
|
||||
lodash.debounce@https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz: {}
|
||||
|
||||
lodash.includes@4.3.0: {}
|
||||
|
||||
lodash.isboolean@3.0.3: {}
|
||||
|
||||
lodash.isinteger@4.0.4: {}
|
||||
|
||||
lodash.isnumber@3.0.3: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.isstring@4.0.1: {}
|
||||
|
||||
lodash.memoize@https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz: {}
|
||||
|
||||
lodash.merge@https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz: {}
|
||||
|
||||
lodash.once@4.1.1: {}
|
||||
|
||||
lodash.template@https://registry.npmmirror.com/lodash.template/-/lodash.template-4.5.0.tgz:
|
||||
dependencies:
|
||||
lodash._reinterpolate: https://registry.npmmirror.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz
|
||||
@@ -8881,8 +8776,8 @@ snapshots:
|
||||
js-yaml: https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz
|
||||
log-symbols: https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz
|
||||
minimatch: https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz
|
||||
ms: https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz
|
||||
nanoid: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz
|
||||
ms: 2.1.3
|
||||
nanoid: 3.3.3
|
||||
serialize-javascript: https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz
|
||||
strip-json-comments: https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz
|
||||
supports-color: https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz
|
||||
@@ -8909,16 +8804,14 @@ snapshots:
|
||||
through2: https://registry.npmmirror.com/through2/-/through2-2.0.5.tgz
|
||||
xtend: https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz
|
||||
|
||||
ms@2.0.0: {}
|
||||
|
||||
ms@2.1.1: {}
|
||||
|
||||
ms@2.1.2: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz: {}
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz: {}
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz: {}
|
||||
|
||||
ms@https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz: {}
|
||||
|
||||
multi-stage-sourcemap@https://registry.npmmirror.com/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz:
|
||||
dependencies:
|
||||
source-map: https://registry.npmmirror.com/source-map/-/source-map-0.1.43.tgz
|
||||
@@ -8941,7 +8834,7 @@ snapshots:
|
||||
mutexify: https://registry.npmmirror.com/mutexify/-/mutexify-1.4.0.tgz
|
||||
pretty-hrtime: https://registry.npmmirror.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz
|
||||
|
||||
nanoid@https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz: {}
|
||||
nanoid@3.3.3: {}
|
||||
|
||||
nanomatch@https://registry.npmmirror.com/nanomatch/-/nanomatch-1.2.13.tgz:
|
||||
dependencies:
|
||||
@@ -9553,8 +9446,6 @@ snapshots:
|
||||
hash-base: https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz
|
||||
inherits: https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-buffer@https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz: {}
|
||||
|
||||
safe-buffer@https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz: {}
|
||||
@@ -9587,8 +9478,6 @@ snapshots:
|
||||
|
||||
semver@6.3.0: {}
|
||||
|
||||
semver@7.6.3: {}
|
||||
|
||||
semver@https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz: {}
|
||||
|
||||
semver@https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz: {}
|
||||
@@ -9610,7 +9499,7 @@ snapshots:
|
||||
fresh: https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz
|
||||
http-errors: https://registry.npmmirror.com/http-errors/-/http-errors-1.7.2.tgz
|
||||
mime: https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz
|
||||
ms: https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz
|
||||
ms: 2.1.1
|
||||
on-finished: https://registry.npmmirror.com/on-finished/-/on-finished-2.3.0.tgz
|
||||
range-parser: https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz
|
||||
statuses: https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz
|
||||
@@ -10236,8 +10125,6 @@ snapshots:
|
||||
|
||||
uuid@https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz: {}
|
||||
|
||||
uuid@https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz: {}
|
||||
|
||||
v8-compile-cache@https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz: {}
|
||||
|
||||
v8flags@https://registry.npmmirror.com/v8flags/-/v8flags-3.2.0.tgz:
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FILES_KEY = 'files';
|
||||
export const MODULES_KEY = 'modules';
|
||||
export const ARTIFACTS_KEY = 'artifacts';
|
||||
export const RULES_KEY = 'rules';
|
||||
export const TOKENS_KEY = 'tokens';
|
||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
||||
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
||||
|
||||
@@ -86,6 +86,16 @@ async function processFn(
|
||||
$options,
|
||||
) {
|
||||
for (const item of operators) {
|
||||
if (item.disabled) {
|
||||
$.log(
|
||||
`Skipping disabled operator: "${
|
||||
item.type
|
||||
}" with arguments:\n >>> ${
|
||||
JSON.stringify(item.args, null, 2) || 'None'
|
||||
}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// process script
|
||||
let script;
|
||||
let $arguments = {};
|
||||
|
||||
@@ -27,6 +27,42 @@ function surge_port_hopping(raw) {
|
||||
};
|
||||
}
|
||||
|
||||
function URI_PROXY() {
|
||||
// socks5+tls
|
||||
// socks5
|
||||
// http, https(可以这么写)
|
||||
const name = 'URI PROXY Parser';
|
||||
const test = (line) => {
|
||||
return /^(socks5\+tls|socks5|http|https):\/\//.test(line);
|
||||
};
|
||||
const parse = (line) => {
|
||||
// parse url
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, type, tls, username, password, server, port, query, name] =
|
||||
line.match(
|
||||
/^(socks5|http|http)(\+tls|s)?:\/\/(?:(.*?):(.*?)@)?(.*?):(\d+?)(\?.*?)?(?:#(.*?))?$/,
|
||||
);
|
||||
|
||||
const proxy = {
|
||||
name:
|
||||
name != null
|
||||
? decodeURIComponent(name)
|
||||
: `${type} ${server}:${port}`,
|
||||
type,
|
||||
tls: tls ? true : false,
|
||||
server,
|
||||
port,
|
||||
username:
|
||||
username != null ? decodeURIComponent(username) : undefined,
|
||||
password:
|
||||
password != null ? decodeURIComponent(password) : undefined,
|
||||
};
|
||||
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||
function URI_SS() {
|
||||
@@ -46,7 +82,15 @@ function URI_SS() {
|
||||
content = content.split('#')[0]; // strip proxy name
|
||||
// handle IPV4 and IPV6
|
||||
let serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||
let userInfoStr = Base64.decode(content.split('@')[0]);
|
||||
|
||||
let rawUserInfoStr = decodeURIComponent(content.split('@')[0]); // 其实应该分隔之后, 用户名和密码再 decodeURIComponent. 但是问题不大
|
||||
let userInfoStr;
|
||||
if (rawUserInfoStr?.startsWith('2022-blake3-')) {
|
||||
userInfoStr = rawUserInfoStr;
|
||||
} else {
|
||||
userInfoStr = Base64.decode(rawUserInfoStr);
|
||||
}
|
||||
|
||||
let query = '';
|
||||
if (!serverAndPortArray) {
|
||||
if (content.includes('?')) {
|
||||
@@ -71,16 +115,21 @@ function URI_SS() {
|
||||
userInfoStr = content.split('@')[0];
|
||||
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
||||
}
|
||||
|
||||
const serverAndPort = serverAndPortArray[1];
|
||||
const portIdx = serverAndPort.lastIndexOf(':');
|
||||
proxy.server = serverAndPort.substring(0, portIdx);
|
||||
proxy.port = `${serverAndPort.substring(portIdx + 1)}`.match(
|
||||
/\d+/,
|
||||
)?.[0];
|
||||
|
||||
const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
||||
proxy.cipher = userInfo[1];
|
||||
proxy.password = userInfo[2];
|
||||
let userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
||||
proxy.cipher = userInfo?.[1];
|
||||
proxy.password = userInfo?.[2];
|
||||
// if (!proxy.cipher || !proxy.password) {
|
||||
// userInfo = rawUserInfoStr.match(/(^.*?):(.*$)/);
|
||||
// proxy.cipher = userInfo?.[1];
|
||||
// proxy.password = userInfo?.[2];
|
||||
// }
|
||||
|
||||
// handle obfs
|
||||
const idx = content.indexOf('?plugin=');
|
||||
@@ -316,7 +365,7 @@ function URI_VMess() {
|
||||
);
|
||||
}
|
||||
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
||||
if (proxy.tls && proxy.sni) {
|
||||
if (proxy.tls && params.sni && params.sni !== '') {
|
||||
proxy.sni = params.sni;
|
||||
}
|
||||
let httpupgrade = false;
|
||||
@@ -340,6 +389,10 @@ function URI_VMess() {
|
||||
} else if (params.net === 'h2' || proxy.network === 'h2') {
|
||||
proxy.network = 'h2';
|
||||
}
|
||||
// 暂不支持 tcp + host + path
|
||||
// else if (params.net === 'tcp' || proxy.network === 'tcp') {
|
||||
// proxy.network = 'tcp';
|
||||
// }
|
||||
if (proxy.network) {
|
||||
let transportHost = params.host ?? params.obfsParam;
|
||||
try {
|
||||
@@ -376,6 +429,7 @@ function URI_VMess() {
|
||||
proxy[`${proxy.network}-opts`] = {
|
||||
'grpc-service-name': getIfNotBlank(transportPath),
|
||||
'_grpc-type': getIfNotBlank(params.type),
|
||||
'_grpc-authority': getIfNotBlank(params.authority),
|
||||
};
|
||||
} else {
|
||||
const opts = {
|
||||
@@ -511,6 +565,9 @@ function URI_VLESS() {
|
||||
}
|
||||
if (params.serviceName) {
|
||||
opts[`${proxy.network}-service-name`] = params.serviceName;
|
||||
if (['grpc'].includes(proxy.network) && params.authority) {
|
||||
opts['_grpc-authority'] = params.authority;
|
||||
}
|
||||
} else if (isShadowrocket && params.path) {
|
||||
if (!['ws', 'http', 'h2'].includes(proxy.network)) {
|
||||
opts[`${proxy.network}-service-name`] = params.path;
|
||||
@@ -838,6 +895,11 @@ function URI_Trojan() {
|
||||
};
|
||||
|
||||
const parse = (line) => {
|
||||
const matched = /^(trojan:\/\/.*?@.*?)(:(\d+))?\/?(\?.*?)?$/.exec(line);
|
||||
const port = matched?.[2];
|
||||
if (!port) {
|
||||
line = line.replace(matched[1], `${matched[1]}:443`);
|
||||
}
|
||||
let [newLine, name] = line.split(/#(.+)/, 2);
|
||||
const parser = getTrojanURIParser();
|
||||
const proxy = parser.parse(newLine);
|
||||
@@ -867,6 +929,8 @@ function Clash_All() {
|
||||
const proxy = JSON.parse(line);
|
||||
if (
|
||||
![
|
||||
'mieru',
|
||||
'juicity',
|
||||
'ss',
|
||||
'ssr',
|
||||
'vmess',
|
||||
@@ -880,6 +944,7 @@ function Clash_All() {
|
||||
'hysteria2',
|
||||
'wireguard',
|
||||
'ssh',
|
||||
'direct',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
throw new Error(
|
||||
@@ -1178,6 +1243,14 @@ function Loon_WireGuard() {
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Surge_Direct() {
|
||||
const name = 'Surge Direct Parser';
|
||||
const test = (line) => {
|
||||
return /^.*=\s*direct/.test(line.split(',')[0]);
|
||||
};
|
||||
const parse = (line) => getSurgeParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
function Surge_SSH() {
|
||||
const name = 'Surge SSH Parser';
|
||||
const test = (line) => {
|
||||
@@ -1357,6 +1430,7 @@ function isIP(ip) {
|
||||
}
|
||||
|
||||
export default [
|
||||
URI_PROXY(),
|
||||
URI_SS(),
|
||||
URI_SSR(),
|
||||
URI_VMess(),
|
||||
@@ -1367,6 +1441,7 @@ export default [
|
||||
URI_Hysteria2(),
|
||||
URI_Trojan(),
|
||||
Clash_All(),
|
||||
Surge_Direct(),
|
||||
Surge_SSH(),
|
||||
Surge_SS(),
|
||||
Surge_VMess(),
|
||||
|
||||
@@ -120,7 +120,7 @@ port = digits:[0-9]+ {
|
||||
method = comma cipher:cipher {
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
|
||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
|
||||
@@ -118,7 +118,7 @@ port = digits:[0-9]+ {
|
||||
method = comma cipher:cipher {
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
|
||||
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
|
||||
@@ -37,7 +37,7 @@ const grammars = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh) {
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh/direct) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@@ -108,15 +108,18 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
|
||||
proxy.type = "hysteria2";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
handleShadowTLS();
|
||||
}
|
||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
||||
proxy.type = "direct";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
@@ -193,7 +196,7 @@ snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh) {
|
||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh/direct) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@@ -106,16 +106,18 @@ hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying
|
||||
proxy.type = "hysteria2";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
handleShadowTLS();
|
||||
}
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_fingerprint/tls_verification/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
proxy.type = "socks5";
|
||||
proxy.tls = true;
|
||||
handleShadowTLS();
|
||||
}
|
||||
|
||||
direct = tag equals "direct" (udp_relay/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/block_quic/others)* {
|
||||
proxy.type = "direct";
|
||||
}
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
@@ -191,7 +193,7 @@ snell_psk = comma "psk" equals match:[^,]+ { proxy.psk = match.join(""); }
|
||||
snell_version = comma "version" equals match:$[0-9]+ { proxy.version = parseInt(match.trim()); }
|
||||
|
||||
usernamek = comma "username" equals match:[^,]+ { proxy.username = match.join(""); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join(""); }
|
||||
passwordk = comma "password" equals match:[^,]+ { proxy.password = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ port = digits:[0-9]+ {
|
||||
params = "?" head:param tail:("&"@param)* {
|
||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||
proxy.sni = params["sni"] || params["peer"];
|
||||
proxy['client-fingerprint'] = params.fp;
|
||||
proxy.alpn = params.alpn ? decodeURIComponent(params.alpn).split(',') : undefined;
|
||||
|
||||
if (toBool(params["ws"])) {
|
||||
proxy.network = "ws";
|
||||
@@ -99,6 +101,7 @@ params = "?" head:param tail:("&"@param)* {
|
||||
proxy[proxy.network + '-opts'] = {
|
||||
'grpc-service-name': params["serviceName"],
|
||||
'_grpc-type': params["mode"],
|
||||
'_grpc-authority': params["authority"],
|
||||
};
|
||||
} else {
|
||||
if (params["path"]) {
|
||||
|
||||
@@ -80,6 +80,8 @@ port = digits:[0-9]+ {
|
||||
params = "?" head:param tail:("&"@param)* {
|
||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||
proxy.sni = params["sni"] || params["peer"];
|
||||
proxy['client-fingerprint'] = params.fp;
|
||||
proxy.alpn = params.alpn ? decodeURIComponent(params.alpn).split(',') : undefined;
|
||||
|
||||
if (toBool(params["ws"])) {
|
||||
proxy.network = "ws";
|
||||
@@ -97,6 +99,7 @@ params = "?" head:param tail:("&"@param)* {
|
||||
proxy[proxy.network + '-opts'] = {
|
||||
'grpc-service-name': params["serviceName"],
|
||||
'_grpc-type': params["mode"],
|
||||
'_grpc-authority': params["authority"],
|
||||
};
|
||||
} else {
|
||||
if (params["path"]) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { hex_md5 } from '@/vendor/md5';
|
||||
import { ProxyUtils } from '@/core/proxy-utils';
|
||||
import { produceArtifact } from '@/restful/sync';
|
||||
import { SETTINGS_KEY } from '@/constants';
|
||||
import YAML from '@/utils/yaml';
|
||||
|
||||
import env from '@/utils/env';
|
||||
import {
|
||||
@@ -21,6 +22,46 @@ import {
|
||||
getRmainingDays,
|
||||
} from '@/utils/flow';
|
||||
|
||||
function isObject(item) {
|
||||
return item && typeof item === 'object' && !Array.isArray(item);
|
||||
}
|
||||
function trimWrap(str) {
|
||||
if (str.startsWith('<') && str.endsWith('>')) {
|
||||
return str.slice(1, -1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
function deepMerge(target, _other) {
|
||||
const other = typeof _other === 'string' ? JSON.parse(_other) : _other;
|
||||
for (const key in other) {
|
||||
if (isObject(other[key])) {
|
||||
if (key.endsWith('!')) {
|
||||
const k = trimWrap(key.slice(0, -1));
|
||||
target[k] = other[key];
|
||||
} else {
|
||||
const k = trimWrap(key);
|
||||
if (!target[k]) Object.assign(target, { [k]: {} });
|
||||
deepMerge(target[k], other[k]);
|
||||
}
|
||||
} else if (Array.isArray(other[key])) {
|
||||
if (key.startsWith('+')) {
|
||||
const k = trimWrap(key.slice(1));
|
||||
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||
target[k] = [...other[key], ...target[k]];
|
||||
} else if (key.endsWith('+')) {
|
||||
const k = trimWrap(key.slice(0, -1));
|
||||
if (!target[k]) Object.assign(target, { [k]: [] });
|
||||
target[k] = [...target[k], ...other[key]];
|
||||
} else {
|
||||
const k = trimWrap(key);
|
||||
Object.assign(target, { [k]: other[key] });
|
||||
}
|
||||
} else {
|
||||
Object.assign(target, { [key]: other[key] });
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
/**
|
||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||
{
|
||||
@@ -321,6 +362,33 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
name: 'Script Operator',
|
||||
func: async (proxies) => {
|
||||
let output = proxies;
|
||||
if (output?.$file?.type === 'mihomoProfile') {
|
||||
try {
|
||||
let patch = YAML.safeLoad(script);
|
||||
if (typeof patch !== 'object') patch = {};
|
||||
output.$content = ProxyUtils.yaml.safeDump(
|
||||
deepMerge(
|
||||
{
|
||||
proxies: await produceArtifact({
|
||||
type:
|
||||
output?.$file?.sourceType ||
|
||||
'collection',
|
||||
name: output?.$file?.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
patch,
|
||||
),
|
||||
);
|
||||
return output;
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
}
|
||||
}
|
||||
await (async function () {
|
||||
const operator = createDynamicFunction(
|
||||
'operator',
|
||||
@@ -339,9 +407,27 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
'operator',
|
||||
`async function operator(input = []) {
|
||||
if (input && (input.$files || input.$content)) {
|
||||
let { $content, $files, $options } = input
|
||||
${script}
|
||||
return { $content, $files, $options }
|
||||
let { $content, $files, $options, $file } = input
|
||||
if($file.type === 'mihomoProfile') {
|
||||
${script}
|
||||
if(typeof main === 'function') {
|
||||
const config = {
|
||||
proxies: await produceArtifact({
|
||||
type: $file.sourceType || 'collection',
|
||||
name: $file.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true
|
||||
}
|
||||
}),
|
||||
}
|
||||
$content = ProxyUtils.yaml.safeDump(await main(config))
|
||||
}
|
||||
} else {
|
||||
${script}
|
||||
}
|
||||
return { $content, $files, $options, $file }
|
||||
} else {
|
||||
let proxies = input
|
||||
let list = []
|
||||
@@ -561,7 +647,7 @@ function ResolveDomainOperator({
|
||||
throw new Error(`域名解析服务提供方 ${provider} 不支持 ${_type}`);
|
||||
}
|
||||
const { defaultTimeout } = $.read(SETTINGS_KEY);
|
||||
const requestTimeout = timeout || defaultTimeout;
|
||||
const requestTimeout = timeout || defaultTimeout || 8000;
|
||||
let type = ['IPv6', 'IP4P'].includes(_type) ? 'IPv6' : 'IPv4';
|
||||
|
||||
const resolver = DOMAIN_RESOLVERS[provider];
|
||||
@@ -699,24 +785,45 @@ function isIP(ip) {
|
||||
|
||||
ResolveDomainOperator.resolver = DOMAIN_RESOLVERS;
|
||||
|
||||
function isAscii(str) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
var pattern = /^[\x00-\x7F]+$/; // ASCII 范围的 Unicode 编码
|
||||
return pattern.test(str);
|
||||
}
|
||||
|
||||
/**************************** Filters ***************************************/
|
||||
// filter useless proxies
|
||||
function UselessFilter() {
|
||||
const KEYWORDS = [
|
||||
'网址',
|
||||
'流量',
|
||||
'时间',
|
||||
'应急',
|
||||
'过期',
|
||||
'Bandwidth',
|
||||
'expire',
|
||||
];
|
||||
return {
|
||||
name: 'Useless Filter',
|
||||
func: RegexFilter({
|
||||
regex: KEYWORDS,
|
||||
keep: false,
|
||||
}).func,
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
if (proxy.cipher && !isAscii(proxy.cipher)) {
|
||||
return false;
|
||||
} else if (proxy.password && !isAscii(proxy.password)) {
|
||||
return false;
|
||||
} else {
|
||||
if (proxy.network) {
|
||||
let transportHosts =
|
||||
proxy[`${proxy.network}-opts`]?.headers?.Host ||
|
||||
proxy[`${proxy.network}-opts`]?.headers?.host;
|
||||
transportHosts = Array.isArray(transportHosts)
|
||||
? transportHosts
|
||||
: [transportHosts];
|
||||
if (
|
||||
transportHosts.some(
|
||||
(host) => host && !isAscii(host),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !/网址|流量|时间|应急|过期|Bandwidth|expire/.test(
|
||||
proxy.name,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ export default function Clash_Producer() {
|
||||
proxy[`${proxy.network}-opts`]
|
||||
) {
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ export default function ClashMeta_Producer() {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||
return false;
|
||||
} else if (['juicity'].includes(proxy.type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
@@ -178,7 +180,7 @@ export default function ClashMeta_Producer() {
|
||||
delete proxy.id;
|
||||
delete proxy.resolved;
|
||||
delete proxy['no-resolve'];
|
||||
if (type !== 'internal') {
|
||||
if (type !== 'internal' || opts['delete-underscore-fields']) {
|
||||
for (const key in proxy) {
|
||||
if (proxy[key] == null || /^_/i.test(key)) {
|
||||
delete proxy[key];
|
||||
@@ -190,6 +192,7 @@ export default function ClashMeta_Producer() {
|
||||
proxy[`${proxy.network}-opts`]
|
||||
) {
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
|
||||
310
backend/src/core/proxy-utils/producers/egern.js
Normal file
310
backend/src/core/proxy-utils/producers/egern.js
Normal file
@@ -0,0 +1,310 @@
|
||||
export default function Egern_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies, type, opts = {}) => {
|
||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||
const list = proxies
|
||||
.filter((proxy) => {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (
|
||||
![
|
||||
'http',
|
||||
'socks5',
|
||||
'ss',
|
||||
'trojan',
|
||||
'hysteria2',
|
||||
'vless',
|
||||
'vmess',
|
||||
].includes(proxy.type) ||
|
||||
(proxy.type === 'ss' &&
|
||||
((proxy.plugin === 'obfs' &&
|
||||
!['http', 'tls'].includes(
|
||||
proxy['plugin-opts']?.mode,
|
||||
)) ||
|
||||
![
|
||||
'chacha20-ietf-poly1305',
|
||||
'chacha20-poly1305',
|
||||
'aes-256-gcm',
|
||||
'aes-128-gcm',
|
||||
'none',
|
||||
'tbale',
|
||||
'rc4',
|
||||
'rc4-md5',
|
||||
'aes-128-cfb',
|
||||
'aes-192-cfb',
|
||||
'aes-256-cfb',
|
||||
'aes-128-ctr',
|
||||
'aes-192-ctr',
|
||||
'aes-256-ctr',
|
||||
'bf-cfb',
|
||||
'camellia-128-cfb',
|
||||
'camellia-192-cfb',
|
||||
'camellia-256-cfb',
|
||||
'cast5-cfb',
|
||||
'des-cfb',
|
||||
'idea-cfb',
|
||||
'rc2-cfb',
|
||||
'seed-cfb',
|
||||
'salsa20',
|
||||
'chacha20',
|
||||
'chacha20-ietf',
|
||||
].includes(proxy.cipher))) ||
|
||||
(proxy.type === 'vmess' &&
|
||||
(![
|
||||
'auto',
|
||||
'aes-128-gcm',
|
||||
'chacha20-poly1305',
|
||||
'none',
|
||||
'zero',
|
||||
].includes(proxy.cipher) ||
|
||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network))) ||
|
||||
(proxy.type === 'trojan' &&
|
||||
!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network) ||
|
||||
(proxy.type === 'vless' &&
|
||||
(typeof proxy.flow !== 'undefined' ||
|
||||
proxy['reality-opts'] ||
|
||||
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
|
||||
proxy.network)))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((proxy) => {
|
||||
if (proxy.tls && !proxy.sni) {
|
||||
proxy.sni = proxy.server;
|
||||
}
|
||||
const prev_hop =
|
||||
proxy.prev_hop ||
|
||||
proxy['underlying-proxy'] ||
|
||||
proxy['dialer-proxy'] ||
|
||||
proxy.detour;
|
||||
|
||||
if (proxy.type === 'http') {
|
||||
proxy = {
|
||||
type: 'http',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
username: proxy.username,
|
||||
password: proxy.password,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
next_hop: proxy.next_hop,
|
||||
};
|
||||
} else if (proxy.type === 'socks5') {
|
||||
proxy = {
|
||||
type: 'socks5',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
username: proxy.username,
|
||||
password: proxy.password,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
};
|
||||
} else if (proxy.type === 'ss') {
|
||||
proxy = {
|
||||
type: 'shadowsocks',
|
||||
name: proxy.name,
|
||||
method:
|
||||
proxy.cipher === 'chacha20-ietf-poly1305'
|
||||
? 'chacha20-poly1305'
|
||||
: proxy.cipher,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
password: proxy.password,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
};
|
||||
if (proxy.plugin === 'obfs') {
|
||||
proxy.obfs = proxy['plugin-opts'].mode;
|
||||
proxy.obfs_host = proxy['plugin-opts'].host;
|
||||
proxy.obfs_uri = proxy['plugin-opts'].path;
|
||||
}
|
||||
} else if (proxy.type === 'hysteria2') {
|
||||
proxy = {
|
||||
type: 'hysteria2',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
auth: proxy.password,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
sni: proxy.sni,
|
||||
skip_tls_verify: proxy['skip-cert-verify'],
|
||||
port_hopping: proxy.ports,
|
||||
port_hopping_interval: proxy['hop-interval'],
|
||||
};
|
||||
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||
proxy.obfs = 'salamander';
|
||||
proxy.obfs_password = proxy['obfs-password'];
|
||||
}
|
||||
} else if (proxy.type === 'trojan') {
|
||||
if (proxy.network === 'ws') {
|
||||
proxy.websocket = {
|
||||
path: proxy['ws-opts']?.path,
|
||||
host: proxy['ws-opts']?.headers?.Host,
|
||||
};
|
||||
}
|
||||
proxy = {
|
||||
type: 'trojan',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
password: proxy.password,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
sni: proxy.sni,
|
||||
skip_tls_verify: proxy['skip-cert-verify'],
|
||||
websocket: proxy.websocket,
|
||||
};
|
||||
} else if (proxy.type === 'vmess') {
|
||||
if (proxy.network === 'ws') {
|
||||
proxy.transport = {
|
||||
[proxy.tls ? 'wss' : 'ws']: {
|
||||
path: proxy['ws-opts']?.path,
|
||||
headers: {
|
||||
Host: proxy['ws-opts']?.headers?.Host,
|
||||
},
|
||||
sni: proxy.tls ? proxy.sni : undefined,
|
||||
skip_tls_verify: proxy.tls
|
||||
? proxy['skip-cert-verify']
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
} else if (proxy.network === 'http') {
|
||||
proxy.transport = {
|
||||
http: {
|
||||
method: proxy['http-opts']?.method,
|
||||
path: proxy['http-opts']?.path,
|
||||
headers: {
|
||||
Host: Array.isArray(
|
||||
proxy['http-opts']?.headers?.Host,
|
||||
)
|
||||
? proxy['http-opts']?.headers?.Host[0]
|
||||
: proxy['http-opts']?.headers?.Host,
|
||||
},
|
||||
skip_tls_verify: proxy['skip-cert-verify'],
|
||||
},
|
||||
};
|
||||
} else if (proxy.network === 'tcp' || !proxy.network) {
|
||||
proxy.transport = {
|
||||
[proxy.tls ? 'tls' : 'tcp']: {
|
||||
sni: proxy.tls ? proxy.sni : undefined,
|
||||
skip_tls_verify: proxy.tls
|
||||
? proxy['skip-cert-verify']
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
proxy = {
|
||||
type: 'vmess',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
user_id: proxy.uuid,
|
||||
security: proxy.cipher,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
legacy: proxy.legacy,
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
transport: proxy.transport,
|
||||
// sni: proxy.sni,
|
||||
// skip_tls_verify: proxy['skip-cert-verify'],
|
||||
};
|
||||
} else if (proxy.type === 'vless') {
|
||||
if (proxy.network === 'ws') {
|
||||
proxy.transport = {
|
||||
[proxy.tls ? 'wss' : 'ws']: {
|
||||
path: proxy['ws-opts']?.path,
|
||||
headers: {
|
||||
Host: proxy['ws-opts']?.headers?.Host,
|
||||
},
|
||||
sni: proxy.tls ? proxy.sni : undefined,
|
||||
skip_tls_verify: proxy.tls
|
||||
? proxy['skip-cert-verify']
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
} else if (proxy.network === 'http') {
|
||||
proxy.transport = {
|
||||
http: {
|
||||
method: proxy['http-opts']?.method,
|
||||
path: proxy['http-opts']?.path,
|
||||
headers: {
|
||||
Host: Array.isArray(
|
||||
proxy['http-opts']?.headers?.Host,
|
||||
)
|
||||
? proxy['http-opts']?.headers?.Host[0]
|
||||
: proxy['http-opts']?.headers?.Host,
|
||||
},
|
||||
skip_tls_verify: proxy['skip-cert-verify'],
|
||||
},
|
||||
};
|
||||
} else if (proxy.network === 'tcp' || !proxy.network) {
|
||||
proxy.transport = {
|
||||
[proxy.tls ? 'tls' : 'tcp']: {
|
||||
sni: proxy.tls ? proxy.sni : undefined,
|
||||
skip_tls_verify: proxy.tls
|
||||
? proxy['skip-cert-verify']
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
proxy = {
|
||||
type: 'vless',
|
||||
name: proxy.name,
|
||||
server: proxy.server,
|
||||
port: proxy.port,
|
||||
user_id: proxy.uuid,
|
||||
security: proxy.cipher,
|
||||
tfo: proxy.tfo || proxy['fast-open'],
|
||||
legacy: proxy.legacy,
|
||||
udp_relay:
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
transport: proxy.transport,
|
||||
// sni: proxy.sni,
|
||||
// skip_tls_verify: proxy['skip-cert-verify'],
|
||||
};
|
||||
}
|
||||
|
||||
delete proxy.subName;
|
||||
delete proxy.collectionName;
|
||||
delete proxy.id;
|
||||
delete proxy.resolved;
|
||||
delete proxy['no-resolve'];
|
||||
if (type !== 'internal') {
|
||||
for (const key in proxy) {
|
||||
if (proxy[key] == null || /^_/i.test(key)) {
|
||||
delete proxy[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
[proxy.type]: {
|
||||
...proxy,
|
||||
type: undefined,
|
||||
prev_hop,
|
||||
},
|
||||
};
|
||||
});
|
||||
return type === 'internal'
|
||||
? list
|
||||
: 'proxies:\n' +
|
||||
list
|
||||
.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n')
|
||||
.join('');
|
||||
};
|
||||
return { type, produce };
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import QX_Producer from './qx';
|
||||
import Shadowrocket_Producer from './shadowrocket';
|
||||
import Surfboard_Producer from './surfboard';
|
||||
import singbox_Producer from './sing-box';
|
||||
import Egern_Producer from './egern';
|
||||
|
||||
function JSON_Producer() {
|
||||
const type = 'ALL';
|
||||
@@ -19,19 +20,37 @@ function JSON_Producer() {
|
||||
}
|
||||
|
||||
export default {
|
||||
qx: QX_Producer(),
|
||||
QX: QX_Producer(),
|
||||
QuantumultX: QX_Producer(),
|
||||
surge: Surge_Producer(),
|
||||
Surge: Surge_Producer(),
|
||||
SurgeMac: SurgeMac_Producer(),
|
||||
Loon: Loon_Producer(),
|
||||
Clash: Clash_Producer(),
|
||||
meta: ClashMeta_Producer(),
|
||||
clashmeta: ClashMeta_Producer(),
|
||||
'clash.meta': ClashMeta_Producer(),
|
||||
'Clash.Meta': ClashMeta_Producer(),
|
||||
ClashMeta: ClashMeta_Producer(),
|
||||
mihomo: ClashMeta_Producer(),
|
||||
Mihomo: ClashMeta_Producer(),
|
||||
uri: URI_Producer(),
|
||||
URI: URI_Producer(),
|
||||
v2: V2Ray_Producer(),
|
||||
v2ray: V2Ray_Producer(),
|
||||
V2Ray: V2Ray_Producer(),
|
||||
json: JSON_Producer(),
|
||||
JSON: JSON_Producer(),
|
||||
stash: Stash_Producer(),
|
||||
Stash: Stash_Producer(),
|
||||
shadowrocket: Shadowrocket_Producer(),
|
||||
Shadowrocket: Shadowrocket_Producer(),
|
||||
ShadowRocket: Shadowrocket_Producer(),
|
||||
surfboard: Surfboard_Producer(),
|
||||
Surfboard: Surfboard_Producer(),
|
||||
singbox: singbox_Producer(),
|
||||
'sing-box': singbox_Producer(),
|
||||
egern: Egern_Producer(),
|
||||
Egern: Egern_Producer(),
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@ import { isPresent, Result } from './utils';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
|
||||
export default function Loon_Producer() {
|
||||
const produce = (proxy) => {
|
||||
const produce = (proxy, type, opts = {}) => {
|
||||
switch (proxy.type) {
|
||||
case 'ss':
|
||||
return shadowsocks(proxy);
|
||||
return shadowsocks(proxy, opts['include-unsupported-proxy']);
|
||||
case 'ssr':
|
||||
return shadowsocksr(proxy);
|
||||
case 'trojan':
|
||||
@@ -56,6 +56,8 @@ function shadowsocks(proxy) {
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
].includes(proxy.cipher)
|
||||
) {
|
||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||
|
||||
@@ -8,6 +8,8 @@ export default function ShadowRocket_Producer() {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||
return false;
|
||||
} else if (['mieru'].includes(proxy.type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
@@ -193,6 +195,7 @@ export default function ShadowRocket_Producer() {
|
||||
proxy[`${proxy.network}-opts`]
|
||||
) {
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
|
||||
@@ -3,7 +3,11 @@ import $ from '@/core/app';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
|
||||
const detourParser = (proxy, parsedProxy) => {
|
||||
if (proxy['dialer-proxy']) parsedProxy.detour = proxy['dialer-proxy'];
|
||||
parsedProxy.detour = proxy['dialer-proxy'] || proxy.detour;
|
||||
};
|
||||
const networkParser = (proxy, parsedProxy) => {
|
||||
if (['tcp', 'udp'].includes(proxy._network))
|
||||
parsedProxy.network = proxy._network;
|
||||
};
|
||||
const tfoParser = (proxy, parsedProxy) => {
|
||||
parsedProxy.tcp_fast_open = false;
|
||||
@@ -214,7 +218,11 @@ const tlsParser = (proxy, parsedProxy) => {
|
||||
proxy['reality-opts']['short-id'];
|
||||
parsedProxy.tls.utls = { enabled: true };
|
||||
}
|
||||
if (proxy['client-fingerprint'] && proxy['client-fingerprint'] !== '')
|
||||
if (
|
||||
!['hysteria', 'hysteria2', 'tuic'].includes(proxy.type) &&
|
||||
proxy['client-fingerprint'] &&
|
||||
proxy['client-fingerprint'] !== ''
|
||||
)
|
||||
parsedProxy.tls.utls = {
|
||||
enabled: true,
|
||||
fingerprint: proxy['client-fingerprint'],
|
||||
@@ -301,6 +309,7 @@ const socks5Parser = (proxy = {}) => {
|
||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
return parsedProxy;
|
||||
@@ -352,6 +361,7 @@ const ssParser = (proxy = {}) => {
|
||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
@@ -466,7 +476,7 @@ const vmessParser = (proxy = {}) => {
|
||||
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
|
||||
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
|
||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
@@ -489,7 +499,7 @@ const vlessParser = (proxy = {}) => {
|
||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
@@ -510,7 +520,7 @@ const trojanParser = (proxy = {}) => {
|
||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
@@ -558,13 +568,14 @@ const hysteriaParser = (proxy = {}) => {
|
||||
parsedProxy.disable_mtu_discovery = true;
|
||||
}
|
||||
}
|
||||
networkParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
return parsedProxy;
|
||||
};
|
||||
const hysteria2Parser = (proxy = {}) => {
|
||||
const hysteria2Parser = (proxy = {}, includeUnsupportedProxy) => {
|
||||
const parsedProxy = {
|
||||
tag: proxy.name,
|
||||
type: 'hysteria2',
|
||||
@@ -576,12 +587,23 @@ const hysteria2Parser = (proxy = {}) => {
|
||||
};
|
||||
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||
throw 'invalid port';
|
||||
if (includeUnsupportedProxy) {
|
||||
if (proxy['hop-interval'])
|
||||
parsedProxy.hop_interval = /^\d+$/.test(proxy['hop-interval'])
|
||||
? `${proxy['hop-interval']}s`
|
||||
: proxy['hop-interval'];
|
||||
if (proxy['ports'])
|
||||
parsedProxy.server_ports = proxy['ports']
|
||||
.split(/\s*,\s*/)
|
||||
.map((p) => p.replace(/\s*-\s*/g, ':'));
|
||||
}
|
||||
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||
if (proxy.obfs === 'salamander') parsedProxy.obfs.type = 'salamander';
|
||||
if (proxy['obfs-password'])
|
||||
parsedProxy.obfs.password = proxy['obfs-password'];
|
||||
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
||||
networkParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
@@ -612,6 +634,7 @@ const tuic5Parser = (proxy = {}) => {
|
||||
if (proxy['udp-over-stream']) parsedProxy.udp_over_stream = true;
|
||||
if (proxy['heartbeat-interval'])
|
||||
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
tlsParser(proxy, parsedProxy);
|
||||
@@ -669,6 +692,7 @@ const wireguardParser = (proxy = {}) => {
|
||||
parsedProxy.peers.push(peer);
|
||||
}
|
||||
}
|
||||
networkParser(proxy, parsedProxy);
|
||||
tfoParser(proxy, parsedProxy);
|
||||
detourParser(proxy, parsedProxy);
|
||||
smuxParser(proxy.smux, parsedProxy);
|
||||
@@ -786,7 +810,12 @@ export default function singbox_Producer() {
|
||||
list.push(hysteriaParser(proxy));
|
||||
break;
|
||||
case 'hysteria2':
|
||||
list.push(hysteria2Parser(proxy));
|
||||
list.push(
|
||||
hysteria2Parser(
|
||||
proxy,
|
||||
opts['include-unsupported-proxy'],
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'tuic':
|
||||
if (!proxy.token || proxy.token.length === 0) {
|
||||
|
||||
@@ -6,7 +6,6 @@ export default function Stash_Producer() {
|
||||
// https://stash.wiki/proxy-protocols/proxy-types#shadowsocks
|
||||
const list = proxies
|
||||
.filter((proxy) => {
|
||||
if (opts['include-unsupported-proxy']) return true;
|
||||
if (
|
||||
![
|
||||
'ss',
|
||||
@@ -40,6 +39,12 @@ export default function Stash_Producer() {
|
||||
'xchacha20',
|
||||
'chacha20-ietf-poly1305',
|
||||
'xchacha20-ietf-poly1305',
|
||||
...(opts['include-unsupported-proxy']
|
||||
? [
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
]
|
||||
: []),
|
||||
].includes(proxy.cipher)) ||
|
||||
(proxy.type === 'snell' && String(proxy.version) === '4') ||
|
||||
(proxy.type === 'vless' && proxy['reality-opts'])
|
||||
@@ -284,6 +289,7 @@ export default function Stash_Producer() {
|
||||
proxy[`${proxy.network}-opts`]
|
||||
) {
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-type'];
|
||||
delete proxy[`${proxy.network}-opts`]['_grpc-authority'];
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
|
||||
@@ -27,6 +27,8 @@ export default function Surge_Producer() {
|
||||
return vmess(proxy, opts['include-unsupported-proxy']);
|
||||
case 'http':
|
||||
return http(proxy);
|
||||
case 'direct':
|
||||
return direct(proxy);
|
||||
case 'socks5':
|
||||
return socks5(proxy);
|
||||
case 'snell':
|
||||
@@ -93,7 +95,7 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||
throw new Error(`cipher ${proxy.cipher} is not supported`);
|
||||
}
|
||||
result.append(`,encrypt-method=${proxy.cipher}`);
|
||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
@@ -193,7 +195,7 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||
function trojan(proxy) {
|
||||
const result = new Result(proxy);
|
||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
@@ -366,7 +368,7 @@ function ssh(proxy) {
|
||||
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
// 所有的类似的字段都有双引号的问题 暂不处理
|
||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
|
||||
// https://manual.nssurge.com/policy/ssh.html
|
||||
// 需配合 Keystore
|
||||
@@ -431,7 +433,7 @@ function http(proxy) {
|
||||
const type = proxy.tls ? 'https' : 'http';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
@@ -503,13 +505,61 @@ function http(proxy) {
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
function direct(proxy) {
|
||||
const result = new Result(proxy);
|
||||
const type = 'direct';
|
||||
result.append(`${proxy.name}=${type}`);
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
|
||||
result.appendIfPresent(
|
||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||
'no-error-alert',
|
||||
);
|
||||
|
||||
// tfo
|
||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||
|
||||
// udp
|
||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||
|
||||
// test-url
|
||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||
result.appendIfPresent(
|
||||
`,test-timeout=${proxy['test-timeout']}`,
|
||||
'test-timeout',
|
||||
);
|
||||
result.appendIfPresent(`,test-udp=${proxy['test-udp']}`, 'test-udp');
|
||||
result.appendIfPresent(`,hybrid=${proxy['hybrid']}`, 'hybrid');
|
||||
result.appendIfPresent(`,tos=${proxy['tos']}`, 'tos');
|
||||
result.appendIfPresent(
|
||||
`,allow-other-interface=${proxy['allow-other-interface']}`,
|
||||
'allow-other-interface',
|
||||
);
|
||||
result.appendIfPresent(
|
||||
`,interface=${proxy['interface-name']}`,
|
||||
'interface-name',
|
||||
);
|
||||
|
||||
// block-quic
|
||||
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||
|
||||
// underlying-proxy
|
||||
result.appendIfPresent(
|
||||
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||
'underlying-proxy',
|
||||
);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
function socks5(proxy) {
|
||||
const result = new Result(proxy);
|
||||
const type = proxy.tls ? 'socks5-tls' : 'socks5';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
|
||||
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||
@@ -675,7 +725,7 @@ function tuic(proxy) {
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
|
||||
result.appendIfPresent(`,uuid=${proxy.uuid}`, 'uuid');
|
||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
result.appendIfPresent(`,token=${proxy.token}`, 'token');
|
||||
|
||||
result.appendIfPresent(
|
||||
@@ -950,7 +1000,7 @@ function hysteria2(proxy) {
|
||||
const result = new Result(proxy);
|
||||
result.append(`${proxy.name}=hysteria2,${proxy.server},${proxy.port}`);
|
||||
|
||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
|
||||
|
||||
if (isPresent(proxy, 'ports')) {
|
||||
result.append(`,port-hopping="${proxy.ports.replace(/,/g, ';')}"`);
|
||||
|
||||
@@ -29,9 +29,13 @@ export default function URI_Producer() {
|
||||
switch (proxy.type) {
|
||||
case 'ss':
|
||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||
result = `ss://${Base64.encode(userinfo)}@${proxy.server}:${
|
||||
proxy.port
|
||||
}${proxy.plugin ? '/' : ''}`;
|
||||
result = `ss://${
|
||||
proxy.cipher?.startsWith('2022-blake3-')
|
||||
? `${encodeURIComponent(
|
||||
proxy.cipher,
|
||||
)}:${encodeURIComponent(proxy.password)}`
|
||||
: Base64.encode(userinfo)
|
||||
}@${proxy.server}:${proxy.port}${proxy.plugin ? '/' : ''}`;
|
||||
if (proxy.plugin) {
|
||||
result += '?plugin=';
|
||||
const opts = proxy['plugin-opts'];
|
||||
@@ -102,7 +106,7 @@ export default function URI_Producer() {
|
||||
port: proxy.port,
|
||||
id: proxy.uuid,
|
||||
type,
|
||||
aid: 0,
|
||||
aid: proxy.alterId || 0,
|
||||
net,
|
||||
tls: proxy.tls ? 'tls' : '',
|
||||
};
|
||||
@@ -134,6 +138,8 @@ export default function URI_Producer() {
|
||||
result.type =
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
|
||||
'gun';
|
||||
result.host =
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
|
||||
}
|
||||
}
|
||||
result = 'vmess://' + Base64.encode(JSON.stringify(result));
|
||||
@@ -196,6 +202,13 @@ export default function URI_Producer() {
|
||||
vlessTransport += `&mode=${encodeURIComponent(
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-type'] || 'gun',
|
||||
)}`;
|
||||
const authority =
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
|
||||
if (authority) {
|
||||
vlessTransport += `&authority=${encodeURIComponent(
|
||||
authority,
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
let vlessTransportServiceName =
|
||||
@@ -261,11 +274,18 @@ export default function URI_Producer() {
|
||||
proxy[`${proxy.network}-opts`]?.[
|
||||
`${proxy.network}-service-name`
|
||||
];
|
||||
let trojanTransportAuthority =
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-authority'];
|
||||
if (trojanTransportServiceName) {
|
||||
trojanTransport += `&serviceName=${encodeURIComponent(
|
||||
trojanTransportServiceName,
|
||||
)}`;
|
||||
}
|
||||
if (trojanTransportAuthority) {
|
||||
trojanTransport += `&authority=${encodeURIComponent(
|
||||
trojanTransportAuthority,
|
||||
)}`;
|
||||
}
|
||||
trojanTransport += `&mode=${encodeURIComponent(
|
||||
proxy[`${proxy.network}-opts`]?.['_grpc-type'] ||
|
||||
'gun',
|
||||
@@ -290,11 +310,27 @@ export default function URI_Producer() {
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
let trojanFp = '';
|
||||
if (proxy['client-fingerprint']) {
|
||||
trojanFp = `&fp=${encodeURIComponent(
|
||||
proxy['client-fingerprint'],
|
||||
)}`;
|
||||
}
|
||||
let trojanAlpn = '';
|
||||
if (proxy.alpn) {
|
||||
trojanAlpn = `&alpn=${encodeURIComponent(
|
||||
Array.isArray(proxy.alpn)
|
||||
? proxy.alpn
|
||||
: proxy.alpn.join(','),
|
||||
)}`;
|
||||
}
|
||||
result = `trojan://${proxy.password}@${proxy.server}:${
|
||||
proxy.port
|
||||
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
|
||||
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
|
||||
}${trojanTransport}#${encodeURIComponent(proxy.name)}`;
|
||||
}${trojanTransport}${trojanAlpn}${trojanFp}#${encodeURIComponent(
|
||||
proxy.name,
|
||||
)}`;
|
||||
break;
|
||||
case 'hysteria2':
|
||||
let hysteria2params = [];
|
||||
|
||||
@@ -37,7 +37,9 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||
if (resourceType === RESOURCE_TYPE.PROXY) {
|
||||
try {
|
||||
let proxies = ProxyUtils.parse(resource);
|
||||
result = ProxyUtils.produce(proxies, 'Loon');
|
||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('解析器: 使用 resource 出现错误');
|
||||
console.log(e.message ?? e);
|
||||
@@ -45,9 +47,20 @@ let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
||||
try {
|
||||
let raw = await download(resourceUrl, arg?.ua, arg?.timeout);
|
||||
let raw = await download(
|
||||
resourceUrl,
|
||||
arg?.ua,
|
||||
arg?.timeout,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
let proxies = ProxyUtils.parse(raw);
|
||||
result = ProxyUtils.produce(proxies, 'Loon');
|
||||
result = ProxyUtils.produce(proxies, 'Loon', undefined, {
|
||||
'include-unsupported-proxy': arg?.includeUnsupportedProxy,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.message ?? e);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import registerSettingRoutes from '@/restful/settings';
|
||||
import registerMiscRoutes from '@/restful/miscs';
|
||||
import registerSortRoutes from '@/restful/sort';
|
||||
import registerFileRoutes from '@/restful/file';
|
||||
import registerTokenRoutes from '@/restful/token';
|
||||
import registerModuleRoutes from '@/restful/module';
|
||||
|
||||
migrate();
|
||||
@@ -32,6 +33,7 @@ function serve() {
|
||||
// register routes
|
||||
registerCollectionRoutes($app);
|
||||
registerSubscriptionRoutes($app);
|
||||
registerTokenRoutes($app);
|
||||
registerFileRoutes($app);
|
||||
registerModuleRoutes($app);
|
||||
registerArtifactRoutes($app);
|
||||
|
||||
@@ -50,11 +50,21 @@ function createCollection(req, res) {
|
||||
|
||||
function getCollection(req, res) {
|
||||
let { name } = req.params;
|
||||
let { raw } = req.query;
|
||||
name = decodeURIComponent(name);
|
||||
const allCols = $.read(COLLECTIONS_KEY);
|
||||
const collection = findByName(allCols, name);
|
||||
if (collection) {
|
||||
success(res, collection);
|
||||
if (raw) {
|
||||
res.set('content-type', 'application/json')
|
||||
.set(
|
||||
'content-disposition',
|
||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||
)
|
||||
.send(JSON.stringify(collection));
|
||||
} else {
|
||||
success(res, collection);
|
||||
}
|
||||
} else {
|
||||
failed(
|
||||
res,
|
||||
|
||||
@@ -13,8 +13,44 @@ import { getISO } from '@/utils/geo';
|
||||
import env from '@/utils/env';
|
||||
|
||||
export default function register($app) {
|
||||
$app.get('/share/col/:name/:target', async (req, res) => {
|
||||
const { target } = req.params;
|
||||
if (target) {
|
||||
req.query.target = target;
|
||||
$.info(`使用路由指定目标: ${target}`);
|
||||
}
|
||||
await downloadCollection(req, res);
|
||||
});
|
||||
$app.get('/share/col/:name', downloadCollection);
|
||||
$app.get('/share/sub/:name/:target', async (req, res) => {
|
||||
const { target } = req.params;
|
||||
if (target) {
|
||||
req.query.target = target;
|
||||
$.info(`使用路由指定目标: ${target}`);
|
||||
}
|
||||
await downloadSubscription(req, res);
|
||||
});
|
||||
$app.get('/share/sub/:name', downloadSubscription);
|
||||
|
||||
$app.get('/download/collection/:name/:target', async (req, res) => {
|
||||
const { target } = req.params;
|
||||
if (target) {
|
||||
req.query.target = target;
|
||||
$.info(`使用路由指定目标: ${target}`);
|
||||
}
|
||||
await downloadCollection(req, res);
|
||||
});
|
||||
$app.get('/download/collection/:name', downloadCollection);
|
||||
$app.get('/download/:name/:target', async (req, res) => {
|
||||
const { target } = req.params;
|
||||
if (target) {
|
||||
req.query.target = target;
|
||||
$.info(`使用路由指定目标: ${target}`);
|
||||
}
|
||||
await downloadSubscription(req, res);
|
||||
});
|
||||
$app.get('/download/:name', downloadSubscription);
|
||||
|
||||
$app.get(
|
||||
'/download/collection/:name/api/v1/server/details',
|
||||
async (req, res) => {
|
||||
@@ -56,11 +92,9 @@ async function downloadSubscription(req, res) {
|
||||
|
||||
const platform =
|
||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||
|
||||
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
|
||||
$.info(
|
||||
`正在下载订阅:${name}\n请求 User-Agent: ${
|
||||
req.headers['user-agent'] || req.headers['User-Agent']
|
||||
}`,
|
||||
`正在下载订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||
);
|
||||
let {
|
||||
url,
|
||||
@@ -72,6 +106,7 @@ async function downloadSubscription(req, res) {
|
||||
includeUnsupportedProxy,
|
||||
resultFormat,
|
||||
proxy,
|
||||
noCache,
|
||||
} = req.query;
|
||||
let $options = {};
|
||||
if (req.query.$options) {
|
||||
@@ -94,6 +129,14 @@ async function downloadSubscription(req, res) {
|
||||
if (url) {
|
||||
url = decodeURIComponent(url);
|
||||
$.info(`指定远程订阅 URL: ${url}`);
|
||||
if (!/^https?:\/\//.test(url)) {
|
||||
content = url;
|
||||
$.info(`URL 不是链接,视为本地订阅`);
|
||||
}
|
||||
}
|
||||
if (content) {
|
||||
content = decodeURIComponent(content);
|
||||
$.info(`指定本地订阅: ${content}`);
|
||||
}
|
||||
if (proxy) {
|
||||
proxy = decodeURIComponent(proxy);
|
||||
@@ -103,10 +146,7 @@ async function downloadSubscription(req, res) {
|
||||
ua = decodeURIComponent(ua);
|
||||
$.info(`指定远程订阅 User-Agent: ${ua}`);
|
||||
}
|
||||
if (content) {
|
||||
content = decodeURIComponent(content);
|
||||
$.info(`指定本地订阅: ${content}`);
|
||||
}
|
||||
|
||||
if (mergeSources) {
|
||||
mergeSources = decodeURIComponent(mergeSources);
|
||||
$.info(`指定合并来源: ${mergeSources}`);
|
||||
@@ -128,10 +168,21 @@ async function downloadSubscription(req, res) {
|
||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||
}
|
||||
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const sub = findByName(allSubs, name);
|
||||
if (sub) {
|
||||
try {
|
||||
const passThroughUA = sub.passThroughUA;
|
||||
if (passThroughUA) {
|
||||
$.info(
|
||||
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${reqUA}`,
|
||||
);
|
||||
ua = reqUA;
|
||||
}
|
||||
let output = await produceArtifact({
|
||||
type: 'subscription',
|
||||
name,
|
||||
@@ -148,8 +199,9 @@ async function downloadSubscription(req, res) {
|
||||
},
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
});
|
||||
|
||||
let flowInfo;
|
||||
if (
|
||||
sub.source !== 'local' ||
|
||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||
@@ -184,7 +236,7 @@ async function downloadSubscription(req, res) {
|
||||
}
|
||||
if (!$arguments.noFlow) {
|
||||
// forward flow headers
|
||||
const flowInfo = await getFlowHeaders(
|
||||
flowInfo = await getFlowHeaders(
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
@@ -204,7 +256,30 @@ async function downloadSubscription(req, res) {
|
||||
}
|
||||
}
|
||||
if (sub.subUserinfo) {
|
||||
res.set('subscription-userinfo', sub.subUserinfo);
|
||||
let subUserInfo;
|
||||
if (/^https?:\/\//.test(sub.subUserinfo)) {
|
||||
try {
|
||||
subUserInfo = await getFlowHeaders(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
proxy || sub.proxy,
|
||||
sub.subUserinfo,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`订阅 ${name} 使用自定义流量链接 ${
|
||||
sub.subUserinfo
|
||||
} 获取流量信息时发生错误: ${JSON.stringify(e)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subUserInfo = sub.subUserinfo;
|
||||
}
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
[subUserInfo, flowInfo].filter((i) => i).join('; '),
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'JSON') {
|
||||
@@ -243,7 +318,7 @@ async function downloadSubscription(req, res) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$.notify(`🌍 Sub-Store 下载订阅失败`, `❌ 未找到订阅:${name}!`);
|
||||
$.error(`🌍 Sub-Store 下载订阅失败\n❌ 未找到订阅:${name}!`);
|
||||
failed(
|
||||
res,
|
||||
new ResourceNotFoundError(
|
||||
@@ -271,7 +346,7 @@ async function downloadCollection(req, res) {
|
||||
$.info(
|
||||
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
||||
req.headers['user-agent'] || req.headers['User-Agent']
|
||||
}`,
|
||||
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
|
||||
);
|
||||
|
||||
let {
|
||||
@@ -280,6 +355,7 @@ async function downloadCollection(req, res) {
|
||||
includeUnsupportedProxy,
|
||||
resultFormat,
|
||||
proxy,
|
||||
noCache,
|
||||
} = req.query;
|
||||
|
||||
let $options = {};
|
||||
@@ -322,6 +398,9 @@ async function downloadCollection(req, res) {
|
||||
if (useMihomoExternal) {
|
||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||
}
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
try {
|
||||
@@ -337,8 +416,9 @@ async function downloadCollection(req, res) {
|
||||
},
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
});
|
||||
|
||||
let subUserInfoOfSub;
|
||||
// forward flow header from the first subscription in this collection
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const subnames = collection.subscriptions;
|
||||
@@ -377,16 +457,13 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
}
|
||||
if (!$arguments.noFlow) {
|
||||
const flowInfo = await getFlowHeaders(
|
||||
subUserInfoOfSub = await getFlowHeaders(
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
proxy || sub.proxy || collection.proxy,
|
||||
$arguments.flowUrl,
|
||||
);
|
||||
if (flowInfo) {
|
||||
res.set('subscription-userinfo', flowInfo);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
@@ -397,10 +474,61 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
}
|
||||
if (sub.subUserinfo) {
|
||||
res.set('subscription-userinfo', sub.subUserinfo);
|
||||
let subUserInfo;
|
||||
if (/^https?:\/\//.test(sub.subUserinfo)) {
|
||||
try {
|
||||
subUserInfo = await getFlowHeaders(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
proxy || sub.proxy,
|
||||
sub.subUserinfo,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`组合订阅 ${name} 使用自定义流量链接 ${
|
||||
sub.subUserinfo
|
||||
} 获取流量信息时发生错误: ${JSON.stringify(e)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subUserInfo = sub.subUserinfo;
|
||||
}
|
||||
subUserInfoOfSub = [subUserInfo, subUserInfoOfSub]
|
||||
.filter((i) => i)
|
||||
.join('; ');
|
||||
}
|
||||
}
|
||||
|
||||
$.info(`组合订阅 ${name} 透传的的流量信息: ${subUserInfoOfSub}`);
|
||||
|
||||
let subUserInfoOfCol;
|
||||
if (/^https?:\/\//.test(collection.subUserinfo)) {
|
||||
try {
|
||||
subUserInfoOfCol = await getFlowHeaders(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
proxy || collection.proxy,
|
||||
collection.subUserinfo,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`组合订阅 ${name} 使用自定义流量链接 ${
|
||||
collection.subUserinfo
|
||||
} 获取流量信息时发生错误: ${JSON.stringify(e)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subUserInfoOfCol = collection.subUserinfo;
|
||||
}
|
||||
res.set(
|
||||
'subscription-userinfo',
|
||||
[subUserInfoOfCol, subUserInfoOfSub]
|
||||
.filter((i) => i)
|
||||
.join('; '),
|
||||
);
|
||||
|
||||
if (platform === 'JSON') {
|
||||
if (resultFormat === 'nezha') {
|
||||
output = nezhaTransform(output);
|
||||
@@ -436,7 +564,7 @@ async function downloadCollection(req, res) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$.notify(
|
||||
$.error(
|
||||
`🌍 Sub-Store 下载组合订阅失败`,
|
||||
`❌ 未找到组合订阅:${name}!`,
|
||||
);
|
||||
|
||||
@@ -13,6 +13,8 @@ import { produceArtifact } from '@/restful/sync';
|
||||
export default function register($app) {
|
||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||
|
||||
$app.get('/share/file/:name', getFile);
|
||||
|
||||
$app.route('/api/file/:name')
|
||||
.get(getFile)
|
||||
.patch(updateFile)
|
||||
@@ -60,6 +62,7 @@ async function getFile(req, res) {
|
||||
mergeSources,
|
||||
ignoreFailedRemoteFile,
|
||||
proxy,
|
||||
noCache,
|
||||
} = req.query;
|
||||
let $options = {};
|
||||
if (req.query.$options) {
|
||||
@@ -111,6 +114,9 @@ async function getFile(req, res) {
|
||||
ignoreFailedRemoteFile = decodeURIComponent(ignoreFailedRemoteFile);
|
||||
$.info(`指定忽略失败的远程文件: ${ignoreFailedRemoteFile}`);
|
||||
}
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
|
||||
const allFiles = $.read(FILES_KEY);
|
||||
const file = findByName(allFiles, name);
|
||||
@@ -126,6 +132,7 @@ async function getFile(req, res) {
|
||||
ignoreFailedRemoteFile,
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -177,7 +184,7 @@ async function getFile(req, res) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$.notify(`🌍 Sub-Store 下载文件失败`, `❌ 未找到文件:${name}!`);
|
||||
$.error(`🌍 Sub-Store 下载文件失败\n❌ 未找到文件:${name}!`);
|
||||
failed(
|
||||
res,
|
||||
new ResourceNotFoundError(
|
||||
@@ -190,11 +197,21 @@ async function getFile(req, res) {
|
||||
}
|
||||
function getWholeFile(req, res) {
|
||||
let { name } = req.params;
|
||||
let { raw } = req.query;
|
||||
name = decodeURIComponent(name);
|
||||
const allFiles = $.read(FILES_KEY);
|
||||
const file = findByName(allFiles, name);
|
||||
if (file) {
|
||||
success(res, file);
|
||||
if (raw) {
|
||||
res.set('content-type', 'application/json')
|
||||
.set(
|
||||
'content-disposition',
|
||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||
)
|
||||
.send(JSON.stringify(file));
|
||||
} else {
|
||||
success(res, file);
|
||||
}
|
||||
} else {
|
||||
failed(
|
||||
res,
|
||||
|
||||
@@ -4,11 +4,13 @@ import migrate from '@/utils/migration';
|
||||
import download from '@/utils/download';
|
||||
import { syncArtifacts } from '@/restful/sync';
|
||||
import { gistBackupAction } from '@/restful/miscs';
|
||||
import { TOKENS_KEY } from '@/constants';
|
||||
|
||||
import registerSubscriptionRoutes from './subscriptions';
|
||||
import registerCollectionRoutes from './collections';
|
||||
import registerArtifactRoutes from './artifacts';
|
||||
import registerFileRoutes from './file';
|
||||
import registerTokenRoutes from './token';
|
||||
import registerModuleRoutes from './module';
|
||||
import registerSyncRoutes from './sync';
|
||||
import registerDownloadRoutes from './download';
|
||||
@@ -36,6 +38,7 @@ export default function serve() {
|
||||
registerSettingRoutes($app);
|
||||
registerArtifactRoutes($app);
|
||||
registerFileRoutes($app);
|
||||
registerTokenRoutes($app);
|
||||
registerModuleRoutes($app);
|
||||
registerSyncRoutes($app);
|
||||
registerNodeInfoRoutes($app);
|
||||
@@ -176,8 +179,6 @@ export default function serve() {
|
||||
fe_be_path === '/' ? '' : fe_be_path
|
||||
}${be_download}`;
|
||||
|
||||
const jwt = eval(`require("jsonwebtoken")`);
|
||||
|
||||
app.use(
|
||||
be_share_rewrite,
|
||||
createProxyMiddleware({
|
||||
@@ -186,31 +187,16 @@ export default function serve() {
|
||||
pathRewrite: (path, req) => {
|
||||
if (req.method.toLowerCase() !== 'get')
|
||||
throw new Error('Method not allowed');
|
||||
const payload = jwt.verify(
|
||||
req.query.token,
|
||||
fe_be_path,
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
const token = tokens.find(
|
||||
(t) =>
|
||||
t.token === req.query.token &&
|
||||
t.type === req.params.type &&
|
||||
t.name === req.params.name &&
|
||||
(t.exp == null || t.exp > Date.now()),
|
||||
);
|
||||
if (
|
||||
payload.type !== req.params.type ||
|
||||
payload.name !== req.params.name
|
||||
)
|
||||
throw new Error('Forbbiden');
|
||||
if (payload.type === 'sub')
|
||||
return path.replace(
|
||||
'/share/sub/',
|
||||
'/download/',
|
||||
);
|
||||
if (payload.type === 'col')
|
||||
return path.replace(
|
||||
'/share/col/',
|
||||
'/download/collection/',
|
||||
);
|
||||
if (payload.type === 'file')
|
||||
return path.replace(
|
||||
'/share/file/',
|
||||
'/api/file/',
|
||||
);
|
||||
throw new Error('Not Found');
|
||||
if (!token) throw new Error('Forbbiden');
|
||||
return path;
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -218,11 +204,13 @@ export default function serve() {
|
||||
be_api_rewrite,
|
||||
createProxyMiddleware({
|
||||
target: `http://127.0.0.1:${port}`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: (path) => {
|
||||
return path.startsWith(be_api_rewrite)
|
||||
const newPath = path.startsWith(be_api_rewrite)
|
||||
? path.replace(be_api_rewrite, be_api)
|
||||
: path;
|
||||
return newPath.includes('?')
|
||||
? `${newPath}&share=true`
|
||||
: `${newPath}?share=true`;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -20,36 +20,6 @@ export default function register($app) {
|
||||
$app.get('/api/utils/env', getEnv); // get runtime environment
|
||||
$app.get('/api/utils/backup', gistBackup); // gist backup actions
|
||||
$app.get('/api/utils/refresh', refresh);
|
||||
$app.post('/api/jwt', (req, res) => {
|
||||
if (!ENV().isNode) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_ENV',
|
||||
`This endpoint is only available in Node.js environment`,
|
||||
),
|
||||
);
|
||||
}
|
||||
try {
|
||||
const { payload, options } = req.body;
|
||||
const jwt = eval(`require("jsonwebtoken")`);
|
||||
const secret = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||
const token = jwt.sign(payload, secret, options);
|
||||
return success(res, {
|
||||
token,
|
||||
secret,
|
||||
});
|
||||
} catch (e) {
|
||||
return failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'JWT_SIGN_FAILED',
|
||||
`Failed to sign JWT token`,
|
||||
`Reason: ${e.message ?? e}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Storage management
|
||||
$app.route('/api/storage')
|
||||
@@ -95,6 +65,9 @@ export default function register($app) {
|
||||
}
|
||||
|
||||
function getEnv(req, res) {
|
||||
if (req.query.share) {
|
||||
env.feature.share = true;
|
||||
}
|
||||
success(res, env);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ async function previewFile(req, res) {
|
||||
const processed =
|
||||
Array.isArray(file.process) && file.process.length > 0
|
||||
? await ProxyUtils.process(
|
||||
{ $files: files, $content: filesContent },
|
||||
{ $files: files, $content: filesContent, $file: file },
|
||||
file.process,
|
||||
)
|
||||
: { $content: filesContent, $files: files };
|
||||
@@ -114,6 +114,10 @@ async function compareSub(req, res) {
|
||||
sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -219,6 +223,10 @@ async function compareCollection(req, res) {
|
||||
sub.ua,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
|
||||
@@ -57,9 +57,29 @@ async function getFlowInfo(req, res) {
|
||||
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||
) {
|
||||
if (sub.subUserinfo) {
|
||||
let subUserInfo;
|
||||
if (/^https?:\/\//.test(sub.subUserinfo)) {
|
||||
try {
|
||||
subUserInfo = await getFlowHeaders(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
sub.subUserinfo,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`订阅 ${name} 使用自定义流量链接 ${
|
||||
sub.subUserinfo
|
||||
} 获取流量信息时发生错误: ${JSON.stringify(e)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subUserInfo = sub.subUserinfo;
|
||||
}
|
||||
try {
|
||||
success(res, {
|
||||
...parseFlowHeaders(sub.subUserinfo),
|
||||
...parseFlowHeaders(subUserInfo),
|
||||
});
|
||||
} catch (e) {
|
||||
$.error(
|
||||
@@ -125,58 +145,73 @@ async function getFlowInfo(req, res) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (sub.subUserinfo) {
|
||||
try {
|
||||
success(res, {
|
||||
...parseFlowHeaders(sub.subUserinfo),
|
||||
remainingDays: getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`Failed to parse flow info for local subscription ${name}: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'NO_FLOW_INFO',
|
||||
'N/A',
|
||||
`Failed to parse flow info`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const flowHeaders = await getFlowHeaders(
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
$arguments.flowUrl,
|
||||
const flowHeaders = await getFlowHeaders(
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
$arguments.flowUrl,
|
||||
);
|
||||
if (!flowHeaders && !sub.subUserinfo) {
|
||||
failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'NO_FLOW_INFO',
|
||||
'No flow info',
|
||||
`Failed to fetch flow headers`,
|
||||
),
|
||||
);
|
||||
if (!flowHeaders) {
|
||||
failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'NO_FLOW_INFO',
|
||||
'No flow info',
|
||||
`Failed to fetch flow headers`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
success(res, {
|
||||
...parseFlowHeaders(flowHeaders),
|
||||
remainingDays: getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
}),
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const remainingDays = getRmainingDays({
|
||||
resetDay: $arguments.resetDay,
|
||||
startDate: $arguments.startDate,
|
||||
cycleDays: $arguments.cycleDays,
|
||||
});
|
||||
let subUserInfo;
|
||||
if (/^https?:\/\//.test(sub.subUserinfo)) {
|
||||
try {
|
||||
subUserInfo = await getFlowHeaders(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
sub.subUserinfo,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`订阅 ${name} 使用自定义流量链接 ${
|
||||
sub.subUserinfo
|
||||
} 获取流量信息时发生错误: ${JSON.stringify(e)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
subUserInfo = sub.subUserinfo;
|
||||
}
|
||||
const result = {
|
||||
...parseFlowHeaders(
|
||||
[subUserInfo, flowHeaders].filter((i) => i).join('; '),
|
||||
),
|
||||
};
|
||||
if (remainingDays != null) {
|
||||
result.remainingDays = remainingDays;
|
||||
}
|
||||
success(res, result);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`Failed to parse flow info for local subscription ${name}: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'NO_FLOW_INFO',
|
||||
'N/A',
|
||||
`Failed to parse flow info`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
failed(
|
||||
|
||||
@@ -39,6 +39,7 @@ async function produceArtifact({
|
||||
awaitCustomCache,
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
}) {
|
||||
platform = platform || 'JSON';
|
||||
|
||||
@@ -72,6 +73,8 @@ async function produceArtifact({
|
||||
proxy || sub.proxy,
|
||||
undefined,
|
||||
awaitCustomCache,
|
||||
noCache || sub.noCache,
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -119,6 +122,8 @@ async function produceArtifact({
|
||||
proxy || sub.proxy,
|
||||
undefined,
|
||||
awaitCustomCache,
|
||||
noCache || sub.noCache,
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -237,6 +242,10 @@ async function produceArtifact({
|
||||
proxy ||
|
||||
sub.proxy ||
|
||||
collection.proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache || sub.noCache,
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -410,6 +419,9 @@ async function produceArtifact({
|
||||
ua || file.ua,
|
||||
undefined,
|
||||
file.proxy || proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -458,6 +470,9 @@ async function produceArtifact({
|
||||
ua || file.ua,
|
||||
undefined,
|
||||
file.proxy || proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -497,7 +512,12 @@ async function produceArtifact({
|
||||
const processed =
|
||||
Array.isArray(file.process) && file.process.length > 0
|
||||
? await ProxyUtils.process(
|
||||
{ $files: files, $content: filesContent, $options },
|
||||
{
|
||||
$files: files,
|
||||
$content: filesContent,
|
||||
$options,
|
||||
$file: file,
|
||||
},
|
||||
file.process,
|
||||
)
|
||||
: { $content: filesContent, $files: files, $options };
|
||||
|
||||
181
backend/src/restful/token.js
Normal file
181
backend/src/restful/token.js
Normal file
@@ -0,0 +1,181 @@
|
||||
import { deleteByName } from '@/utils/database';
|
||||
import { ENV } from '@/vendor/open-api';
|
||||
import { TOKENS_KEY, SUBS_KEY, FILES_KEY, COLLECTIONS_KEY } from '@/constants';
|
||||
import { failed, success } from '@/restful/response';
|
||||
import $ from '@/core/app';
|
||||
import { RequestInvalidError, InternalServerError } from '@/restful/errors';
|
||||
|
||||
export default function register($app) {
|
||||
if (!$.read(TOKENS_KEY)) $.write([], TOKENS_KEY);
|
||||
|
||||
$app.post('/api/token', signToken);
|
||||
|
||||
$app.route('/api/token/:token').delete(deleteToken);
|
||||
|
||||
$app.route('/api/tokens').get(getAllTokens);
|
||||
}
|
||||
|
||||
function deleteToken(req, res) {
|
||||
let { token } = req.params;
|
||||
token = decodeURIComponent(token);
|
||||
$.info(`正在删除:${token}`);
|
||||
let allTokens = $.read(TOKENS_KEY);
|
||||
deleteByName(allTokens, token, 'token');
|
||||
$.write(allTokens, TOKENS_KEY);
|
||||
success(res);
|
||||
}
|
||||
|
||||
function getAllTokens(req, res) {
|
||||
const { type, name } = req.query;
|
||||
const allTokens = $.read(TOKENS_KEY) || [];
|
||||
success(
|
||||
res,
|
||||
type || name
|
||||
? allTokens.filter(
|
||||
(item) =>
|
||||
(type ? item.type === type : true) &&
|
||||
(name ? item.name === name : true),
|
||||
)
|
||||
: allTokens,
|
||||
);
|
||||
}
|
||||
|
||||
async function signToken(req, res) {
|
||||
if (!ENV().isNode) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_ENV',
|
||||
`This endpoint is only available in Node.js environment`,
|
||||
),
|
||||
);
|
||||
}
|
||||
try {
|
||||
const { payload, options } = req.body;
|
||||
const ms = eval(`require("ms")`);
|
||||
let token = payload?.token;
|
||||
if (token != null) {
|
||||
if (typeof token !== 'string' || token.length < 1) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_CUSTOM_TOKEN',
|
||||
`Invalid custom token: ${token}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
if (tokens.find((t) => t.token === token)) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'DUPLICATE_TOKEN',
|
||||
`Token ${token} already exists`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
const type = payload?.type;
|
||||
const name = payload?.name;
|
||||
if (!type || !name)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_PAYLOAD',
|
||||
`payload type and name are required`,
|
||||
),
|
||||
);
|
||||
if (type === 'col') {
|
||||
const collections = $.read(COLLECTIONS_KEY) || [];
|
||||
const collection = collections.find((c) => c.name === name);
|
||||
if (!collection)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_COLLECTION',
|
||||
`collection ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else if (type === 'file') {
|
||||
const files = $.read(FILES_KEY) || [];
|
||||
const file = files.find((f) => f.name === name);
|
||||
if (!file)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_FILE',
|
||||
`file ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else if (type === 'sub') {
|
||||
const subs = $.read(SUBS_KEY) || [];
|
||||
const sub = subs.find((s) => s.name === name);
|
||||
if (!sub)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_SUB',
|
||||
`sub ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_TYPE',
|
||||
`type ${name} not supported`,
|
||||
),
|
||||
);
|
||||
}
|
||||
let expiresIn = options?.expiresIn;
|
||||
if (options?.expiresIn != null) {
|
||||
expiresIn = ms(options.expiresIn);
|
||||
if (expiresIn == null || isNaN(expiresIn) || expiresIn <= 0) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_EXPIRES_IN',
|
||||
`Invalid expiresIn option: ${options.expiresIn}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// const secret = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||
const nanoid = eval(`require("nanoid")`);
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
// const now = Date.now();
|
||||
// for (const key in tokens) {
|
||||
// const token = tokens[key];
|
||||
// if (token.exp != null || token.exp < now) {
|
||||
// delete tokens[key];
|
||||
// }
|
||||
// }
|
||||
if (!token) {
|
||||
do {
|
||||
token = nanoid.customAlphabet(nanoid.urlAlphabet)();
|
||||
} while (tokens.find((t) => t.token === token));
|
||||
}
|
||||
tokens.push({
|
||||
...payload,
|
||||
token,
|
||||
createdAt: Date.now(),
|
||||
expiresIn: expiresIn > 0 ? options?.expiresIn : undefined,
|
||||
exp: expiresIn > 0 ? Date.now() + expiresIn : undefined,
|
||||
});
|
||||
|
||||
$.write(tokens, TOKENS_KEY);
|
||||
return success(res, {
|
||||
token,
|
||||
// secret,
|
||||
});
|
||||
} catch (e) {
|
||||
return failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'TOKEN_SIGN_FAILED',
|
||||
`Failed to sign token`,
|
||||
`Reason: ${e.message ?? e}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
export function findByName(list, name) {
|
||||
return list.find((item) => item.name === name);
|
||||
export function findByName(list, name, field = 'name') {
|
||||
return list.find((item) => item[field] === name);
|
||||
}
|
||||
|
||||
export function findIndexByName(list, name) {
|
||||
return list.findIndex((item) => item.name === name);
|
||||
export function findIndexByName(list, name, field = 'name') {
|
||||
return list.findIndex((item) => item[field] === name);
|
||||
}
|
||||
|
||||
export function deleteByName(list, name) {
|
||||
const idx = findIndexByName(list, name);
|
||||
export function deleteByName(list, name, field = 'name') {
|
||||
const idx = findIndexByName(list, name, field);
|
||||
list.splice(idx, 1);
|
||||
}
|
||||
|
||||
export function updateByName(list, name, newItem) {
|
||||
const idx = findIndexByName(list, name);
|
||||
export function updateByName(list, name, newItem, field = 'name') {
|
||||
const idx = findIndexByName(list, name, field);
|
||||
list[idx] = newItem;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import $ from '@/core/app';
|
||||
import dnsPacket from 'dns-packet';
|
||||
import { Buffer } from 'buffer';
|
||||
import { isIPv4 } from '@/utils';
|
||||
|
||||
export async function doh({ url, domain, type = 'A', timeout, edns }) {
|
||||
const buf = dnsPacket.encode({
|
||||
@@ -23,7 +24,7 @@ export async function doh({ url, domain, type = 'A', timeout, edns }) {
|
||||
{
|
||||
code: 'CLIENT_SUBNET',
|
||||
ip: edns,
|
||||
sourcePrefixLength: 24,
|
||||
sourcePrefixLength: isIPv4(edns) ? 24 : 56,
|
||||
scopePrefixLength: 0,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
validCheck,
|
||||
} from '@/utils/flow';
|
||||
import $ from '@/core/app';
|
||||
import PROXY_PREPROCESSORS from '@/core/proxy-utils/preprocessors';
|
||||
const clashPreprocessor = PROXY_PREPROCESSORS.find(
|
||||
(processor) => processor.name === 'Clash Pre-processor',
|
||||
);
|
||||
|
||||
const tasks = new Map();
|
||||
|
||||
@@ -21,6 +25,8 @@ export default async function download(
|
||||
customProxy,
|
||||
skipCustomCache,
|
||||
awaitCustomCache,
|
||||
noCache,
|
||||
preprocess,
|
||||
) {
|
||||
let $arguments = {};
|
||||
let url = rawUrl.replace(/#noFlow$/, '');
|
||||
@@ -43,14 +49,19 @@ export default async function download(
|
||||
}
|
||||
}
|
||||
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||
const { defaultProxy, defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||
$.read(SETTINGS_KEY);
|
||||
const {
|
||||
defaultProxy,
|
||||
defaultUserAgent,
|
||||
defaultTimeout,
|
||||
cacheThreshold: defaultCacheThreshold,
|
||||
} = $.read(SETTINGS_KEY);
|
||||
const cacheThreshold = defaultCacheThreshold || 1024;
|
||||
let proxy = customProxy || defaultProxy;
|
||||
if ($.env.isNode) {
|
||||
proxy = proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||
}
|
||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||
const requestTimeout = timeout || defaultTimeout;
|
||||
const requestTimeout = timeout || defaultTimeout || 8000;
|
||||
const id = hex_md5(userAgent + url);
|
||||
|
||||
if ($arguments?.cacheKey === true) {
|
||||
@@ -65,7 +76,7 @@ export default async function download(
|
||||
if (customCacheKey && !skipCustomCache) {
|
||||
const customCached = $.read(customCacheKey);
|
||||
const cached = resourceCache.get(id);
|
||||
if (!$arguments?.noCache && cached) {
|
||||
if (!noCache && !$arguments?.noCache && cached) {
|
||||
$.info(
|
||||
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
||||
);
|
||||
@@ -81,6 +92,9 @@ export default async function download(
|
||||
timeout,
|
||||
proxy,
|
||||
true,
|
||||
undefined,
|
||||
undefined,
|
||||
preprocess,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
@@ -101,6 +115,9 @@ export default async function download(
|
||||
timeout,
|
||||
proxy,
|
||||
true,
|
||||
undefined,
|
||||
undefined,
|
||||
preprocess,
|
||||
).catch((e) => {
|
||||
$.error(
|
||||
`乐观缓存: URL ${url} 异步更新缓存发生错误 ${
|
||||
@@ -149,8 +166,8 @@ export default async function download(
|
||||
|
||||
// try to find in app cache
|
||||
const cached = resourceCache.get(id);
|
||||
if (!$arguments?.noCache && cached) {
|
||||
$.info(`使用缓存: ${url}`);
|
||||
if (!noCache && !$arguments?.noCache && cached) {
|
||||
$.info(`使用缓存: ${url}, ${userAgent}`);
|
||||
result = cached;
|
||||
if (customCacheKey) {
|
||||
$.info(`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`);
|
||||
@@ -163,10 +180,10 @@ export default async function download(
|
||||
: { insecure: true }
|
||||
: undefined;
|
||||
$.info(
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nInsecure: ${!!insecure}\nURL: ${url}`,
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nInsecure: ${!!insecure}\nPreprocess: ${preprocess}\nURL: ${url}`,
|
||||
);
|
||||
try {
|
||||
const { body, headers } = await http.get({
|
||||
let { body, headers, statusCode } = await http.get({
|
||||
url,
|
||||
...(proxy ? { proxy } : {}),
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
@@ -174,15 +191,28 @@ export default async function download(
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
...(insecure ? insecure : {}),
|
||||
});
|
||||
$.info(`statusCode: ${statusCode}`);
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
throw new Error(`statusCode: ${statusCode}`);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
const flowInfo = getFlowField(headers);
|
||||
if (flowInfo) {
|
||||
headersResourceCache.set(url, flowInfo);
|
||||
headersResourceCache.set(id, flowInfo);
|
||||
}
|
||||
}
|
||||
if (body.replace(/\s/g, '').length === 0)
|
||||
throw new Error(new Error('远程资源内容为空'));
|
||||
if (preprocess) {
|
||||
try {
|
||||
if (clashPreprocessor.test(body)) {
|
||||
body = clashPreprocessor.parse(body);
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(`Clash Pre-processor error: ${e}`);
|
||||
}
|
||||
}
|
||||
let shouldCache = true;
|
||||
if (cacheThreshold) {
|
||||
const size = body.length / 1024;
|
||||
|
||||
@@ -44,10 +44,6 @@ try {
|
||||
meta.plugin = $Plugin;
|
||||
}
|
||||
if (isNode) {
|
||||
const secret = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||
if (secret && eval('process.env.SUB_STORE_FRONTEND_PATH')) {
|
||||
feature.share = true;
|
||||
}
|
||||
meta.node = {
|
||||
version: eval('process.version'),
|
||||
argv: eval('process.argv'),
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
import { SETTINGS_KEY } from '@/constants';
|
||||
import { HTTP, ENV } from '@/vendor/open-api';
|
||||
import { hex_md5 } from '@/vendor/md5';
|
||||
import { getPolicyDescriptor } from '@/utils';
|
||||
import $ from '@/core/app';
|
||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||
|
||||
export function getFlowField(headers) {
|
||||
const subkey = Object.keys(headers).filter((k) =>
|
||||
/SUBSCRIPTION-USERINFO/i.test(k),
|
||||
)[0];
|
||||
return headers[subkey];
|
||||
const keys = Object.keys(headers);
|
||||
let sub = '';
|
||||
let webPage = '';
|
||||
for (let k of keys) {
|
||||
const lower = k.toLowerCase();
|
||||
if (lower === 'subscription-userinfo') {
|
||||
sub = headers[k];
|
||||
} else if (lower === 'profile-web-page-url') {
|
||||
webPage = headers[k];
|
||||
}
|
||||
}
|
||||
|
||||
return `${sub || ''}${
|
||||
webPage ? `; app_url=${encodeURIComponent(webPage)}` : ''
|
||||
}`;
|
||||
}
|
||||
export async function getFlowHeaders(
|
||||
rawUrl,
|
||||
@@ -41,29 +53,26 @@ export async function getFlowHeaders(
|
||||
return;
|
||||
}
|
||||
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||
const cached = headersResourceCache.get(url);
|
||||
const insecure = $arguments?.insecure
|
||||
? $.env.isNode
|
||||
? { strictSSL: false }
|
||||
: { insecure: true }
|
||||
: undefined;
|
||||
const { defaultProxy, defaultFlowUserAgent, defaultTimeout } =
|
||||
$.read(SETTINGS_KEY);
|
||||
let proxy = customProxy || defaultProxy;
|
||||
if ($.env.isNode) {
|
||||
proxy = proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||
}
|
||||
const userAgent = ua || defaultFlowUserAgent || 'clash';
|
||||
const requestTimeout = timeout || defaultTimeout || 8000;
|
||||
const id = hex_md5(userAgent + url);
|
||||
const cached = headersResourceCache.get(id);
|
||||
let flowInfo;
|
||||
if (!$arguments?.noCache && cached) {
|
||||
// $.info(`使用缓存的流量信息: ${url}`);
|
||||
$.info(`使用缓存的流量信息: ${url}, ${userAgent}`);
|
||||
flowInfo = cached;
|
||||
} else {
|
||||
const insecure = $arguments?.insecure
|
||||
? $.env.isNode
|
||||
? { strictSSL: false }
|
||||
: { insecure: true }
|
||||
: undefined;
|
||||
const { defaultProxy, defaultFlowUserAgent, defaultTimeout } =
|
||||
$.read(SETTINGS_KEY);
|
||||
let proxy = customProxy || defaultProxy;
|
||||
if ($.env.isNode) {
|
||||
proxy =
|
||||
proxy || eval('process.env.SUB_STORE_BACKEND_DEFAULT_PROXY');
|
||||
}
|
||||
const userAgent =
|
||||
ua ||
|
||||
defaultFlowUserAgent ||
|
||||
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
||||
const requestTimeout = timeout || defaultTimeout;
|
||||
const http = HTTP();
|
||||
if (flowUrl) {
|
||||
$.info(
|
||||
@@ -159,7 +168,10 @@ export async function getFlowHeaders(
|
||||
}
|
||||
}
|
||||
if (flowInfo) {
|
||||
headersResourceCache.set(url, flowInfo);
|
||||
flowInfo = flowInfo.trim();
|
||||
}
|
||||
if (flowInfo) {
|
||||
headersResourceCache.set(id, flowInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +202,29 @@ export function parseFlowHeaders(flowHeaders) {
|
||||
? Number(expireMatch[1] + expireMatch[2])
|
||||
: undefined;
|
||||
|
||||
return { expires, total, usage: { upload, download } };
|
||||
const remainingDaysMatch = flowHeaders.match(/reset_day=([0-9]+)/);
|
||||
const remainingDays = remainingDaysMatch
|
||||
? Number(remainingDaysMatch[1])
|
||||
: undefined;
|
||||
|
||||
const appUrlMatch = flowHeaders.match(/app_url=(.*?)\s*?(;|$)/);
|
||||
const appUrl = appUrlMatch ? decodeURIComponent(appUrlMatch[1]) : undefined;
|
||||
|
||||
const planNameMatch = flowHeaders.match(/plan_name=(.*?)\s*?(;|$)/);
|
||||
const planName = planNameMatch
|
||||
? decodeURIComponent(planNameMatch[1])
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
expires,
|
||||
total,
|
||||
usage: { upload, download },
|
||||
remainingDays,
|
||||
appUrl,
|
||||
planName,
|
||||
};
|
||||
}
|
||||
|
||||
export function flowTransfer(flow, unit = 'B') {
|
||||
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
let unitIndex = unitList.indexOf(unit);
|
||||
|
||||
@@ -17,6 +17,7 @@ const ISOFlags = {
|
||||
'🇧🇪': ['BE', 'BEL'],
|
||||
'🇧🇬': ['BG', 'BGR'],
|
||||
'🇧🇭': ['BH', 'BHR'],
|
||||
'🇧🇴': ['BO', 'BOL'],
|
||||
'🇧🇷': ['BR', 'BRA'],
|
||||
'🇧🇾': ['BY', 'BLR'],
|
||||
'🇨🇦': ['CA', 'CAN'],
|
||||
@@ -38,6 +39,7 @@ const ISOFlags = {
|
||||
'🇬🇧': ['GB', 'GBR', 'UK'],
|
||||
'🇬🇪': ['GE', 'GEO'],
|
||||
'🇬🇷': ['GR', 'GRC'],
|
||||
'🇬🇹': ['GT', 'GTM'],
|
||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||
'🇭🇷': ['HR', 'HRV'],
|
||||
'🇭🇺': ['HU', 'HUN'],
|
||||
@@ -141,6 +143,7 @@ export function getFlag(name) {
|
||||
'🇧🇭': ['Bahrain', '巴林'],
|
||||
'🇧🇷': ['Brazil', '巴西', '圣保罗'],
|
||||
'🇧🇾': ['Belarus', '白俄罗斯', '白俄'],
|
||||
'🇧🇴': ['Bolivia', '玻利维亚'],
|
||||
'🇨🇦': [
|
||||
'Canada',
|
||||
'加拿大',
|
||||
@@ -151,6 +154,7 @@ export function getFlag(name) {
|
||||
'滑铁卢',
|
||||
'多伦多',
|
||||
'Waterloo',
|
||||
'Toronto',
|
||||
],
|
||||
'🇨🇭': ['Switzerland', '瑞士', '苏黎世', 'Zurich'],
|
||||
'🇨🇱': ['Chile', '智利'],
|
||||
@@ -190,6 +194,7 @@ export function getFlag(name) {
|
||||
],
|
||||
'🇬🇪': ['Georgia', '格鲁吉亚', '格魯吉亞'],
|
||||
'🇬🇷': ['Greece', '希腊', '希臘'],
|
||||
'🇬🇹': ['Guatemala', '危地马拉'],
|
||||
'🇭🇰': [
|
||||
'Hongkong',
|
||||
'香港',
|
||||
@@ -245,7 +250,7 @@ export function getFlag(name) {
|
||||
'🇮🇪': ['Ireland', '爱尔兰', '愛爾蘭', '都柏林'],
|
||||
'🇮🇱': ['Israel', '以色列'],
|
||||
'🇮🇲': ['Isle of Man', '马恩岛', '馬恩島'],
|
||||
'🇮🇳': ['India', '印度', '孟买', 'MFumbai'],
|
||||
'🇮🇳': ['India', '印度', '孟买', 'MFumbai', 'Mumbai'],
|
||||
'🇮🇷': ['Iran', '伊朗'],
|
||||
'🇮🇸': ['Iceland', '冰岛', '冰島'],
|
||||
'🇮🇹': ['Italy', '意大利', '義大利', '米兰', 'Nachash'],
|
||||
@@ -261,7 +266,14 @@ export function getFlag(name) {
|
||||
'🇲🇹': ['Malta', '马耳他'],
|
||||
'🇲🇽': ['Mexico', '墨西哥'],
|
||||
'🇲🇾': ['Malaysia', '马来', '馬來', '吉隆坡', '大馬'],
|
||||
'🇳🇱': ['Netherlands', '荷兰', '荷蘭', '尼德蘭', '阿姆斯特丹'],
|
||||
'🇳🇱': [
|
||||
'Netherlands',
|
||||
'荷兰',
|
||||
'荷蘭',
|
||||
'尼德蘭',
|
||||
'阿姆斯特丹',
|
||||
'Amsterdam',
|
||||
],
|
||||
'🇳🇴': ['Norway', '挪威'],
|
||||
'🇳🇵': ['Nepal', '尼泊尔'],
|
||||
'🇳🇿': ['New Zealand', '新西兰', '新西蘭'],
|
||||
@@ -269,7 +281,7 @@ export function getFlag(name) {
|
||||
'🇵🇪': ['Peru', '秘鲁', '祕魯'],
|
||||
'🇵🇭': ['Philippines', '菲律宾', '菲律賓'],
|
||||
'🇵🇰': ['Pakistan', '巴基斯坦'],
|
||||
'🇵🇱': ['Poland', '波兰', '波蘭'],
|
||||
'🇵🇱': ['Poland', '波兰', '波蘭', '华沙', 'Warsaw'],
|
||||
'🇵🇷': ['Puerto Rico', '波多黎各'],
|
||||
'🇵🇹': ['Portugal', '葡萄牙'],
|
||||
'🇵🇾': ['Paraguay', '巴拉圭'],
|
||||
@@ -294,7 +306,7 @@ export function getFlag(name) {
|
||||
'Moscow',
|
||||
],
|
||||
'🇸🇦': ['Saudi', '沙特阿拉伯', '沙特', 'Riyadh', '利雅得'],
|
||||
'🇸🇪': ['Sweden', '瑞典'],
|
||||
'🇸🇪': ['Sweden', '瑞典', '斯德哥尔摩', 'Stockholm'],
|
||||
'🇸🇬': [
|
||||
'Singapore',
|
||||
'新加坡',
|
||||
@@ -314,7 +326,7 @@ export function getFlag(name) {
|
||||
'🇸🇰': ['Slovakia', '斯洛伐克'],
|
||||
'🇹🇭': ['Thailand', '泰国', '泰國', '曼谷'],
|
||||
'🇹🇳': ['Tunisia', '突尼斯'],
|
||||
'🇹🇷': ['Turkey', '土耳其', '伊斯坦布尔'],
|
||||
'🇹🇷': ['Turkey', '土耳其', '伊斯坦布尔', 'Istanbul'],
|
||||
'🇹🇼': [
|
||||
'Taiwan',
|
||||
'台湾',
|
||||
@@ -341,6 +353,7 @@ export function getFlag(name) {
|
||||
'波特兰',
|
||||
'达拉斯',
|
||||
'俄勒冈',
|
||||
'Oregon',
|
||||
'凤凰城',
|
||||
'费利蒙',
|
||||
'硅谷',
|
||||
@@ -354,10 +367,17 @@ export function getFlag(name) {
|
||||
'沪美',
|
||||
'哥伦布',
|
||||
'纽约',
|
||||
'New York',
|
||||
'Los Angeles',
|
||||
'San Jose',
|
||||
'Sillicon Valley',
|
||||
'Michigan',
|
||||
'俄亥俄',
|
||||
'Ohio',
|
||||
'马纳萨斯',
|
||||
'Manassas',
|
||||
'弗吉尼亚',
|
||||
'Virginia',
|
||||
],
|
||||
'🇺🇾': ['Uruguay', '乌拉圭'],
|
||||
'🇻🇪': ['Venezuela', '委内瑞拉'],
|
||||
@@ -418,8 +438,12 @@ export function getFlag(name) {
|
||||
RegExp(`(^|[^a-zA-Z])${keyword}([^a-zA-Z]|$)`).test(name),
|
||||
)
|
||||
) {
|
||||
//console.log(`ISOFlag = ${flag}`)
|
||||
return (Flag = flag);
|
||||
const isCN2 =
|
||||
flag == '🇨🇳' &&
|
||||
RegExp(`(^|[^a-zA-Z])CN2([^a-zA-Z]|$)`).test(name);
|
||||
if (!isCN2) {
|
||||
return (Flag = flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,4 +498,7 @@ export class MMDB {
|
||||
ipaso(ip) {
|
||||
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
||||
}
|
||||
ipasn(ip) {
|
||||
return this.asnReader?.asn(ip)?.autonomousSystemNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class Gist {
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
timeout,
|
||||
timeout: timeout || 8000,
|
||||
|
||||
events: {
|
||||
onResponse: (resp) => {
|
||||
@@ -81,7 +81,7 @@ export default class Gist {
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
timeout,
|
||||
timeout: timeout || 8000,
|
||||
|
||||
events: {
|
||||
onResponse: (resp) => {
|
||||
|
||||
@@ -2,18 +2,24 @@ export function getUserAgentFromHeaders(headers) {
|
||||
const keys = Object.keys(headers);
|
||||
let UA = '';
|
||||
let ua = '';
|
||||
let accept = '';
|
||||
for (let k of keys) {
|
||||
if (/USER-AGENT/i.test(k)) {
|
||||
const lower = k.toLowerCase();
|
||||
if (lower === 'user-agent') {
|
||||
UA = headers[k];
|
||||
ua = UA.toLowerCase();
|
||||
break;
|
||||
} else if (lower === 'accept') {
|
||||
accept = headers[k];
|
||||
}
|
||||
}
|
||||
return { UA, ua };
|
||||
return { UA, ua, accept };
|
||||
}
|
||||
export function getPlatformFromUserAgent({ ua, UA }) {
|
||||
|
||||
export function getPlatformFromUserAgent({ ua, UA, accept }) {
|
||||
if (UA.indexOf('Quantumult%20X') !== -1) {
|
||||
return 'QX';
|
||||
} else if (ua.indexOf('egern') !== -1) {
|
||||
return 'Egern';
|
||||
} else if (UA.indexOf('Surfboard') !== -1) {
|
||||
return 'Surfboard';
|
||||
} else if (UA.indexOf('Surge Mac') !== -1) {
|
||||
@@ -39,11 +45,14 @@ export function getPlatformFromUserAgent({ ua, UA }) {
|
||||
return 'V2Ray';
|
||||
} else if (ua.indexOf('sing-box') !== -1) {
|
||||
return 'sing-box';
|
||||
} else {
|
||||
} else if (accept.indexOf('application/json') === 0) {
|
||||
return 'JSON';
|
||||
} else {
|
||||
return 'V2Ray';
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlatformFromHeaders(headers) {
|
||||
const { UA, ua } = getUserAgentFromHeaders(headers);
|
||||
return getPlatformFromUserAgent({ ua, UA });
|
||||
const { UA, ua, accept } = getUserAgentFromHeaders(headers);
|
||||
return getPlatformFromUserAgent({ ua, UA, accept });
|
||||
}
|
||||
|
||||
1
backend/src/vendor/express.js
vendored
1
backend/src/vendor/express.js
vendored
@@ -9,6 +9,7 @@ export default function express({ substore: $, port, host }) {
|
||||
'Access-Control-Allow-Methods': 'POST,GET,OPTIONS,PATCH,PUT,DELETE',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Origin, X-Requested-With, Content-Type, Accept',
|
||||
'X-Powered-By': 'Sub-Store',
|
||||
};
|
||||
|
||||
// node support
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
name: Sub-Store
|
||||
description: "支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *"
|
||||
description: '支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *'
|
||||
compat_arguments:
|
||||
ability: http-client-policy
|
||||
cronexp: 55 23 * * *
|
||||
sync: '"Sub-Store Sync"'
|
||||
timeout: "120"
|
||||
timeout: '120'
|
||||
engine: auto
|
||||
produce: '"# Sub-Store Produce"'
|
||||
produce_cronexp: 50 */6 * * *
|
||||
@@ -12,26 +12,27 @@ compat_arguments:
|
||||
produce_col: '"col1,col2"'
|
||||
compat_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顺序: 并发执行单条订阅, 然后并发执行组合订阅'
|
||||
scriptings:
|
||||
- http_request:
|
||||
name: Sub-Store Core
|
||||
match: ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info)))
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js
|
||||
body_required: true
|
||||
- http_request:
|
||||
name: Sub-Store Simple
|
||||
match: ^https?:\/\/sub\.store
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js
|
||||
body_required: true
|
||||
- schedule:
|
||||
name: "{{{sync}}}"
|
||||
cron: "{{{cronexp}}}"
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js
|
||||
- schedule:
|
||||
name: "{{{produce}}}"
|
||||
cron: "{{{produce_cronexp}}}"
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js
|
||||
arguments:
|
||||
_compat.$argument: '"sub={{{produce_sub}}}&col={{{produce_col}}}"'
|
||||
- http_request:
|
||||
name: Sub-Store Core
|
||||
match: ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info)))
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js
|
||||
body_required: true
|
||||
- http_request:
|
||||
name: Sub-Store Simple
|
||||
match: ^https?:\/\/sub\.store
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js
|
||||
body_required: true
|
||||
- schedule:
|
||||
name: '{{{sync}}}'
|
||||
cron: '{{{cronexp}}}'
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js
|
||||
- schedule:
|
||||
name: '{{{produce}}}'
|
||||
cron: '{{{produce_cronexp}}}'
|
||||
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js
|
||||
arguments:
|
||||
_compat.$argument: '"sub={{{produce_sub}}}&col={{{produce_col}}}"'
|
||||
mitm:
|
||||
hostnames:
|
||||
includes:
|
||||
- sub.store
|
||||
|
||||
@@ -21,6 +21,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// 13. `test-url` 为测延迟链接, `test-timeout` 为测延迟超时
|
||||
// 14. `ports` 为端口跳跃, `hop-interval` 变换端口号的时间间隔
|
||||
// 15. `ip-version` 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer. 会进行内部转换, 若无法匹配则使用原始值
|
||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||
|
||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||
|
||||
@@ -67,6 +68,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||
// Gist, // Gist 类
|
||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||
// }
|
||||
|
||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||
@@ -164,6 +166,20 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
|
||||
// 若不存在 `source._collection`, 说明输出结果为单条订阅, 脚本设置在此单条订阅上
|
||||
|
||||
// 这个历史遗留原因, 是有点复杂. 提供一个例子, 用来取当前脚本所在的组合订阅或单条订阅名称
|
||||
|
||||
// let name = ''
|
||||
// for (const [key, value] of Object.entries(env.source)) {
|
||||
// if (!key.startsWith('_')) {
|
||||
// name = value.displayName || value.name
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if (!name) {
|
||||
// const collection = env.source._collection
|
||||
// name = collection.displayName || collection.name
|
||||
// }
|
||||
|
||||
// 1. 输出单条订阅 sub-1 时, 该单条订阅中的脚本上下文为:
|
||||
// {
|
||||
// "source": {
|
||||
|
||||
Reference in New Issue
Block a user