mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cd0545023 | ||
|
|
b6f848a6e6 | ||
|
|
99d058bcf1 | ||
|
|
533103e765 | ||
|
|
cf82764171 | ||
|
|
7b783c1fe3 | ||
|
|
372eff9a44 | ||
|
|
d3b5a529d7 | ||
|
|
8049134bb5 | ||
|
|
3f620700a4 | ||
|
|
9e64a68481 | ||
|
|
9ce5916414 | ||
|
|
047c21fe70 | ||
|
|
47849dc6d0 | ||
|
|
af06086c1b | ||
|
|
4a6a147667 | ||
|
|
c6540d14cd | ||
|
|
3db71ec531 | ||
|
|
cf156c2f17 | ||
|
|
e28e2a78fb | ||
|
|
b0a2c709e8 | ||
|
|
5dc2c8ced7 | ||
|
|
d2a65ee0fe | ||
|
|
4dd4ae98ca | ||
|
|
0d41eb467f | ||
|
|
ba1c91a7a5 | ||
|
|
30fa87c172 | ||
|
|
1eaa33948b | ||
|
|
619e256ed8 | ||
|
|
b46209e164 | ||
|
|
a1ba4e273e | ||
|
|
bfc95ed92a | ||
|
|
32f591ec56 | ||
|
|
cea16d8c44 | ||
|
|
93a1ba7b50 | ||
|
|
e6d1aa1150 | ||
|
|
26e83798da | ||
|
|
b083d2d840 | ||
|
|
cf35afcab2 | ||
|
|
d073dfeef8 | ||
|
|
f970ea3361 | ||
|
|
630bac0575 | ||
|
|
7f3cb2b191 | ||
|
|
92e1e4a0fb | ||
|
|
3b85d313fe | ||
|
|
c91d8e28e4 | ||
|
|
8cbb4492be | ||
|
|
6f7da57e3a | ||
|
|
2586c29746 | ||
|
|
6f7fe8204b | ||
|
|
bafaf07743 | ||
|
|
9962eb0947 | ||
|
|
ac5232a7bc | ||
|
|
2301ccbfb5 | ||
|
|
0b5761e5fc | ||
|
|
3ab21b0e26 | ||
|
|
89ab72e46c | ||
|
|
18bd6526d0 | ||
|
|
c7329c32eb | ||
|
|
4819ae95e4 | ||
|
|
370d228b04 | ||
|
|
d092916168 | ||
|
|
0c93de48ab | ||
|
|
274aa50373 | ||
|
|
e24de8d0b6 | ||
|
|
93a5ce6c3b | ||
|
|
cb66c8daa2 | ||
|
|
f4cdc953e6 | ||
|
|
2a1c2eb9df | ||
|
|
6217c2e5cd | ||
|
|
f90d9c2fd1 | ||
|
|
3e952e9e88 | ||
|
|
a81b55f752 | ||
|
|
33652af516 | ||
|
|
2bca669930 | ||
|
|
f1bf0e1e8d | ||
|
|
16b9cd9aaf | ||
|
|
32eb069ab2 | ||
|
|
4c9f8011c7 | ||
|
|
bd26b0a561 | ||
|
|
958d1e52c8 | ||
|
|
e7a2e60963 | ||
|
|
fa6a274f79 | ||
|
|
e40b3f88d5 | ||
|
|
163ad9ee09 | ||
|
|
abb6f2dec1 | ||
|
|
56870bbd5f | ||
|
|
efbc6ecd84 | ||
|
|
c27c589024 | ||
|
|
0efed4f1a0 | ||
|
|
e3a514d1fb | ||
|
|
64478c7a27 | ||
|
|
dc8f19f350 | ||
|
|
b4ccfc7e07 | ||
|
|
3f1940630a | ||
|
|
5a0bdb1276 | ||
|
|
a1b86e26a2 | ||
|
|
6ec8c29f6a | ||
|
|
bbb9602f9f | ||
|
|
6db6153672 | ||
|
|
b66189948a | ||
|
|
2611dccc73 | ||
|
|
25d3cf6ca4 | ||
|
|
3637c5eb74 | ||
|
|
80d46597b4 | ||
|
|
ca65e4209e | ||
|
|
53bb4866e7 | ||
|
|
09495fa607 | ||
|
|
4b27d40602 | ||
|
|
518de2e919 | ||
|
|
078bf228de | ||
|
|
aaef97cf5d | ||
|
|
7beff4013f | ||
|
|
23cf81d0a5 | ||
|
|
572f2f5533 | ||
|
|
1c6d761e09 | ||
|
|
437297b8b0 | ||
|
|
ca437865e6 | ||
|
|
739100c873 | ||
|
|
a4384f4f13 | ||
|
|
468d136f0e | ||
|
|
b0c1157fe1 | ||
|
|
56626dabc7 | ||
|
|
2a87f7b3c3 | ||
|
|
81adfbc461 | ||
|
|
e04217c50d | ||
|
|
391a5aa2e4 | ||
|
|
2f3b42f552 | ||
|
|
76302f9d53 | ||
|
|
1924e9735c | ||
|
|
6a8cee3cd5 | ||
|
|
9b2209cc8b | ||
|
|
c585785c50 | ||
|
|
c3e5da7ee4 | ||
|
|
2ecad8bbb8 | ||
|
|
a642213928 | ||
|
|
f85e360ea8 | ||
|
|
1b948cdf52 | ||
|
|
556d5f393c | ||
|
|
a8c05207c0 | ||
|
|
e97fb1e6d9 | ||
|
|
e40b9a77c4 | ||
|
|
df0ac8a218 | ||
|
|
36377f3c20 | ||
|
|
47f26bdac8 | ||
|
|
a8a89ee2a2 | ||
|
|
199c5fb337 | ||
|
|
df505616ec | ||
|
|
c5b11f8b36 | ||
|
|
b19b49d2fa |
23
.github/workflows/main.yml
vendored
23
.github/workflows/main.yml
vendored
@@ -21,11 +21,11 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "20"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
cd backend && pnpm i
|
cd backend && pnpm i --no-frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
@@ -44,14 +44,20 @@ jobs:
|
|||||||
cd backend
|
cd backend
|
||||||
SUBSTORE_RELEASE=`node --eval="process.stdout.write(require('./package.json').version)"`
|
SUBSTORE_RELEASE=`node --eval="process.stdout.write(require('./package.json').version)"`
|
||||||
echo "release_tag=$SUBSTORE_RELEASE" >> $GITHUB_OUTPUT
|
echo "release_tag=$SUBSTORE_RELEASE" >> $GITHUB_OUTPUT
|
||||||
|
- name: Prepare release
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
pnpm i -D conventional-changelog-cli
|
||||||
|
pnpm run changelog
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
body_path: ./backend/CHANGELOG.md
|
||||||
tag_name: ${{ steps.tag.outputs.release_tag }}
|
tag_name: ${{ steps.tag.outputs.release_tag }}
|
||||||
generate_release_notes: true
|
# generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
./backend/sub-store.min.js
|
./backend/sub-store.min.js
|
||||||
./backend/dist/sub-store-0.min.js
|
./backend/dist/sub-store-0.min.js
|
||||||
@@ -59,6 +65,17 @@ jobs:
|
|||||||
./backend/dist/sub-store-parser.loon.min.js
|
./backend/dist/sub-store-parser.loon.min.js
|
||||||
./backend/dist/cron-sync-artifacts.min.js
|
./backend/dist/cron-sync-artifacts.min.js
|
||||||
./backend/dist/sub-store.bundle.js
|
./backend/dist/sub-store.bundle.js
|
||||||
|
- name: Git push assets to "release" branch
|
||||||
|
run: |
|
||||||
|
cd backend/dist || exit 1
|
||||||
|
git init
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git checkout -b release
|
||||||
|
git add .
|
||||||
|
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
|
- name: Sync to GitLab
|
||||||
env:
|
env:
|
||||||
GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}
|
GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "web"]
|
|
||||||
path = web
|
|
||||||
url = https://github.com/sub-store-org/Sub-Store-Front-End.git
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -11,7 +11,7 @@ Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
[](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml)     
|
||||||
|
<a href="https://trendshift.io/repositories/4572" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4572" alt="sub-store-org%2FSub-Store | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
[](https://www.buymeacoffee.com/PengYM)
|
[](https://www.buymeacoffee.com/PengYM)
|
||||||
|
|
||||||
Core functionalities:
|
Core functionalities:
|
||||||
@@ -26,15 +26,13 @@ Core functionalities:
|
|||||||
|
|
||||||
### Supported Input Formats
|
### Supported Input Formats
|
||||||
|
|
||||||
- [x] SS URI
|
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||||
- [x] SSR URI
|
- [x] Clash Proxies YAML
|
||||||
- [x] SSD URI
|
- [x] Clash Proxy JSON(single line)
|
||||||
- [x] V2RayN URI
|
|
||||||
- [x] Hysteria 2 URI
|
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, WireGuard, VLESS, Hysteria 2)
|
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||||
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, TUIC, Snell, Hysteria 2, SSR(external, only for macOS), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), SSR(external, only for macOS), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, WireGuard(Surfboard to Surfboard))
|
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||||
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
|
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.205",
|
"version": "2.14.332",
|
||||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,13 +9,15 @@
|
|||||||
"serve": "node sub-store.min.js",
|
"serve": "node sub-store.min.js",
|
||||||
"start": "nodemon -w src -w package.json --exec babel-node src/main.js",
|
"start": "nodemon -w src -w package.json --exec babel-node src/main.js",
|
||||||
"dev:esbuild": "nodemon -w src -w package.json dev-esbuild.js",
|
"dev:esbuild": "nodemon -w src -w package.json dev-esbuild.js",
|
||||||
"dev:run": "nodemon -w sub-store.json -w sub-store.min.js sub-store.min.js",
|
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"bundle": "node bundle.js"
|
"bundle": "node bundle.js",
|
||||||
|
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
||||||
},
|
},
|
||||||
"author": "Peng-YM",
|
"author": "Peng-YM",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@maxmind/geoip2-node": "^5.0.0",
|
||||||
"automerge": "1.0.1-preview.7",
|
"automerge": "1.0.1-preview.7",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"connect-history-api-fallback": "^2.0.0",
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
|
|||||||
63
backend/pnpm-lock.yaml
generated
63
backend/pnpm-lock.yaml
generated
@@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@maxmind/geoip2-node':
|
||||||
|
specifier: ^5.0.0
|
||||||
|
version: registry.npmmirror.com/@maxmind/geoip2-node@5.0.0
|
||||||
automerge:
|
automerge:
|
||||||
specifier: 1.0.1-preview.7
|
specifier: 1.0.1-preview.7
|
||||||
version: registry.npmmirror.com/automerge@1.0.1-preview.7
|
version: registry.npmmirror.com/automerge@1.0.1-preview.7
|
||||||
@@ -1967,6 +1970,15 @@ packages:
|
|||||||
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.13
|
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.13
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/@maxmind/geoip2-node@5.0.0:
|
||||||
|
resolution: {integrity: sha512-ki+q5//oU4tZ3BAhegZJcB5czoZyic5JSTEKbrUAQB/BzAoAiGyLW0immEmQvVVyy2SMlvBTJ3zqyRj8K9BbwQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@maxmind/geoip2-node/-/geoip2-node-5.0.0.tgz}
|
||||||
|
name: '@maxmind/geoip2-node'
|
||||||
|
version: 5.0.0
|
||||||
|
dependencies:
|
||||||
|
ip6addr: registry.npmmirror.com/ip6addr@0.2.5
|
||||||
|
maxmind: registry.npmmirror.com/maxmind@4.3.19
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/@sindresorhus/is@0.14.0:
|
registry.npmmirror.com/@sindresorhus/is@0.14.0:
|
||||||
resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz}
|
resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz}
|
||||||
name: '@sindresorhus/is'
|
name: '@sindresorhus/is'
|
||||||
@@ -5988,6 +6000,15 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/ip6addr@0.2.5:
|
||||||
|
resolution: {integrity: sha512-9RGGSB6Zc9Ox5DpDGFnJdIeF0AsqXzdH+FspCfPPaU/L/4tI6P+5lIoFUFm9JXs9IrJv1boqAaNCQmoDADTSKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ip6addr/-/ip6addr-0.2.5.tgz}
|
||||||
|
name: ip6addr
|
||||||
|
version: 0.2.5
|
||||||
|
dependencies:
|
||||||
|
assert-plus: registry.npmmirror.com/assert-plus@1.0.0
|
||||||
|
jsprim: registry.npmmirror.com/jsprim@2.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/ipaddr.js@1.9.1:
|
registry.npmmirror.com/ipaddr.js@1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz}
|
||||||
name: ipaddr.js
|
name: ipaddr.js
|
||||||
@@ -6556,6 +6577,12 @@ packages:
|
|||||||
version: 0.2.3
|
version: 0.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/json-schema@0.4.0:
|
||||||
|
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz}
|
||||||
|
name: json-schema
|
||||||
|
version: 0.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/json-stable-stringify-without-jsonify@1.0.1:
|
registry.npmmirror.com/json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
|
||||||
name: json-stable-stringify-without-jsonify
|
name: json-stable-stringify-without-jsonify
|
||||||
@@ -6595,6 +6622,18 @@ packages:
|
|||||||
verror: registry.npmmirror.com/verror@1.10.0
|
verror: registry.npmmirror.com/verror@1.10.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
registry.npmmirror.com/jsprim@2.0.2:
|
||||||
|
resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/jsprim/-/jsprim-2.0.2.tgz}
|
||||||
|
name: jsprim
|
||||||
|
version: 2.0.2
|
||||||
|
engines: {'0': node >=0.6.0}
|
||||||
|
dependencies:
|
||||||
|
assert-plus: registry.npmmirror.com/assert-plus@1.0.0
|
||||||
|
extsprintf: registry.npmmirror.com/extsprintf@1.3.0
|
||||||
|
json-schema: registry.npmmirror.com/json-schema@0.4.0
|
||||||
|
verror: registry.npmmirror.com/verror@1.10.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/just-debounce@1.1.0:
|
registry.npmmirror.com/just-debounce@1.1.0:
|
||||||
resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz}
|
resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/just-debounce/-/just-debounce-1.1.0.tgz}
|
||||||
name: just-debounce
|
name: just-debounce
|
||||||
@@ -6943,6 +6982,16 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/maxmind@4.3.19:
|
||||||
|
resolution: {integrity: sha512-Bu/VEN7ZWAOCjifdZaXJQuN6/yO7+OK35pnJsqmz8sOndK3KQFvJoY+6HX09/MmLLqtCfa+sMK0iaQOaTejGNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/maxmind/-/maxmind-4.3.19.tgz}
|
||||||
|
name: maxmind
|
||||||
|
version: 4.3.19
|
||||||
|
engines: {node: '>=12', npm: '>=6'}
|
||||||
|
dependencies:
|
||||||
|
mmdb-lib: registry.npmmirror.com/mmdb-lib@2.1.0
|
||||||
|
tiny-lru: registry.npmmirror.com/tiny-lru@11.2.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/md5.js@1.3.5:
|
registry.npmmirror.com/md5.js@1.3.5:
|
||||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz}
|
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz}
|
||||||
name: md5.js
|
name: md5.js
|
||||||
@@ -7132,6 +7181,13 @@ packages:
|
|||||||
version: 0.5.3
|
version: 0.5.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/mmdb-lib@2.1.0:
|
||||||
|
resolution: {integrity: sha512-tdDTZmnI5G4UoSctv2KxM/3VQt2XRj4CmR5R4VsAWsOUcS3LysHR34wtixWm/pXxXdkBDuN92auxkC0T2+qd1Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mmdb-lib/-/mmdb-lib-2.1.0.tgz}
|
||||||
|
name: mmdb-lib
|
||||||
|
version: 2.1.0
|
||||||
|
engines: {node: '>=10', npm: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/mocha@10.0.0:
|
registry.npmmirror.com/mocha@10.0.0:
|
||||||
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz}
|
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz}
|
||||||
name: mocha
|
name: mocha
|
||||||
@@ -9424,6 +9480,13 @@ packages:
|
|||||||
process: registry.npmmirror.com/process@0.11.10
|
process: registry.npmmirror.com/process@0.11.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
registry.npmmirror.com/tiny-lru@11.2.6:
|
||||||
|
resolution: {integrity: sha512-0PU3c9PjMnltZaFo2sGYv/nnJsMjG0Cxx8X6FXHPPGjFyoo1SJDxvUXW1207rdiSxYizf31roo+GrkIByQeZoA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tiny-lru/-/tiny-lru-11.2.6.tgz}
|
||||||
|
name: tiny-lru
|
||||||
|
version: 11.2.6
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
registry.npmmirror.com/tinyify@3.0.0:
|
registry.npmmirror.com/tinyify@3.0.0:
|
||||||
resolution: {integrity: sha512-RtjVjC1xwwxt8AMVfxEmo+FzRJB6p5sAOtFaJj8vMrkWShtArsM4dLVRWhx2Vc07Me3NWgmP7pi9UPm/a2XNNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinyify/-/tinyify-3.0.0.tgz}
|
resolution: {integrity: sha512-RtjVjC1xwwxt8AMVfxEmo+FzRJB6p5sAOtFaJj8vMrkWShtArsM4dLVRWhx2Vc07Me3NWgmP7pi9UPm/a2XNNA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinyify/-/tinyify-3.0.0.tgz}
|
||||||
name: tinyify
|
name: tinyify
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
import YAML from '@/utils/yaml';
|
import YAML from '@/utils/yaml';
|
||||||
import download from '@/utils/download';
|
import download from '@/utils/download';
|
||||||
import { isIPv4, isIPv6, isValidPortNumber } from '@/utils';
|
import {
|
||||||
|
isIPv4,
|
||||||
|
isIPv6,
|
||||||
|
isValidPortNumber,
|
||||||
|
isNotBlank,
|
||||||
|
utf8ArrayToStr,
|
||||||
|
} from '@/utils';
|
||||||
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
||||||
import PROXY_PREPROCESSORS from './preprocessors';
|
import PROXY_PREPROCESSORS from './preprocessors';
|
||||||
import PROXY_PRODUCERS from './producers';
|
import PROXY_PRODUCERS from './producers';
|
||||||
import PROXY_PARSERS from './parsers';
|
import PROXY_PARSERS from './parsers';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
||||||
|
import { findByName } from '@/utils/database';
|
||||||
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||||
|
import Gist from '@/utils/gist';
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
@@ -95,18 +106,50 @@ async function processFn(proxies, operators = [], targetPlatform, source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
url = `${url.split('#')[0]}${noCache ? '#noCache' : ''}`;
|
||||||
|
const downloadUrlMatch = url.match(
|
||||||
|
/^\/api\/(file|module)\/(.+)/,
|
||||||
|
);
|
||||||
|
if (downloadUrlMatch) {
|
||||||
|
let type = '';
|
||||||
|
try {
|
||||||
|
type = downloadUrlMatch?.[1];
|
||||||
|
let name = downloadUrlMatch?.[2];
|
||||||
|
if (name == null) {
|
||||||
|
throw new Error(`本地 ${type} URL 无效: ${url}`);
|
||||||
|
}
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
||||||
|
const item = findByName($.read(key), name);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error(`找不到 ${type}: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
// if this is a remote script, download it
|
if (type === 'module') {
|
||||||
try {
|
script = item.content;
|
||||||
script = await download(
|
} else {
|
||||||
`${url.split('#')[0]}${noCache ? '#noCache' : ''}`,
|
script = await produceArtifact({
|
||||||
);
|
type: 'file',
|
||||||
// $.info(`Script loaded: >>>\n ${script}`);
|
name,
|
||||||
} catch (err) {
|
});
|
||||||
$.error(
|
}
|
||||||
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`,
|
} catch (err) {
|
||||||
);
|
$.error(
|
||||||
throw new Error(`无法下载脚本: ${url}`);
|
`Error when loading ${type}: ${item.args.content}.\n Reason: ${err}`,
|
||||||
|
);
|
||||||
|
throw new Error(`无法加载 ${type}: ${url}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if this is a remote script, download it
|
||||||
|
try {
|
||||||
|
script = await download(url);
|
||||||
|
// $.info(`Script loaded: >>>\n ${script}`);
|
||||||
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`,
|
||||||
|
);
|
||||||
|
throw new Error(`无法下载脚本: ${url}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
script = content;
|
script = content;
|
||||||
@@ -118,7 +161,7 @@ async function processFn(proxies, operators = [], targetPlatform, source) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.info(
|
$.log(
|
||||||
`Applying "${item.type}" with arguments:\n >>> ${
|
`Applying "${item.type}" with arguments:\n >>> ${
|
||||||
JSON.stringify(item.args, null, 2) || 'None'
|
JSON.stringify(item.args, null, 2) || 'None'
|
||||||
}`,
|
}`,
|
||||||
@@ -145,16 +188,43 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sni_off_supported = /Surge|SurgeMac|Shadowrocket/i.test(
|
||||||
|
targetPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter(
|
proxies = proxies.filter(
|
||||||
(proxy) =>
|
(proxy) =>
|
||||||
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
||||||
);
|
);
|
||||||
|
|
||||||
$.info(`Producing proxies for target: ${targetPlatform}`);
|
proxies = proxies.map((proxy) => {
|
||||||
|
proxy._subName = proxy.subName;
|
||||||
|
proxy._collectionName = proxy.collectionName;
|
||||||
|
proxy._resolved = proxy.resolved;
|
||||||
|
|
||||||
|
if (!isNotBlank(proxy.name)) {
|
||||||
|
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||||
|
}
|
||||||
|
if (proxy['disable-sni']) {
|
||||||
|
if (sni_off_supported) {
|
||||||
|
proxy.sni = 'off';
|
||||||
|
} else if (!['tuic'].includes(proxy.type)) {
|
||||||
|
$.error(
|
||||||
|
`Target platform ${targetPlatform} does not support sni off. Proxy's fields (sni, tls-fingerprint and skip-cert-verify) will be modified.`,
|
||||||
|
);
|
||||||
|
proxy.sni = '';
|
||||||
|
proxy['skip-cert-verify'] = true;
|
||||||
|
delete proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
|
});
|
||||||
|
|
||||||
|
$.log(`Producing proxies for target: ${targetPlatform}`);
|
||||||
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
||||||
let localPort = 10000;
|
let localPort = 10000;
|
||||||
const list = proxies
|
let list = proxies
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
try {
|
try {
|
||||||
let line = producer.produce(proxy, type, opts);
|
let line = producer.produce(proxy, type, opts);
|
||||||
@@ -180,7 +250,18 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((line) => line.length > 0);
|
.filter((line) => line.length > 0);
|
||||||
return type === 'internal' ? list : list.join('\n');
|
list = type === 'internal' ? list : list.join('\n');
|
||||||
|
if (
|
||||||
|
targetPlatform.startsWith('Surge') &&
|
||||||
|
proxies.length > 0 &&
|
||||||
|
proxies.every((p) => p.type === 'wireguard')
|
||||||
|
) {
|
||||||
|
list = `#!name=${proxies[0]?.subName}
|
||||||
|
#!desc=${proxies[0]?._desc ?? ''}
|
||||||
|
#!category=${proxies[0]?._category ?? ''}
|
||||||
|
${list}`;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
} else if (producer.type === 'ALL') {
|
} else if (producer.type === 'ALL') {
|
||||||
return producer.produce(proxies, type, opts);
|
return producer.produce(proxies, type, opts);
|
||||||
}
|
}
|
||||||
@@ -194,6 +275,11 @@ export const ProxyUtils = {
|
|||||||
isIPv6,
|
isIPv6,
|
||||||
isIP,
|
isIP,
|
||||||
yaml: YAML,
|
yaml: YAML,
|
||||||
|
getFlag,
|
||||||
|
removeFlag,
|
||||||
|
getISO,
|
||||||
|
MMDB,
|
||||||
|
Gist,
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@@ -215,6 +301,10 @@ function safeMatch(parser, line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lastParse(proxy) {
|
function lastParse(proxy) {
|
||||||
|
if (proxy.interface) {
|
||||||
|
proxy['interface-name'] = proxy.interface;
|
||||||
|
delete proxy.interface;
|
||||||
|
}
|
||||||
if (isValidPortNumber(proxy.port)) {
|
if (isValidPortNumber(proxy.port)) {
|
||||||
proxy.port = parseInt(proxy.port, 10);
|
proxy.port = parseInt(proxy.port, 10);
|
||||||
}
|
}
|
||||||
@@ -318,6 +408,10 @@ function lastParse(proxy) {
|
|||||||
delete proxy.ports;
|
delete proxy.ports;
|
||||||
}
|
}
|
||||||
if (['vless'].includes(proxy.type)) {
|
if (['vless'].includes(proxy.type)) {
|
||||||
|
// 非 reality, 空 flow 没有意义
|
||||||
|
if (!proxy['reality-opts'] && !proxy.flow) {
|
||||||
|
delete proxy.flow;
|
||||||
|
}
|
||||||
if (['http'].includes(proxy.network)) {
|
if (['http'].includes(proxy.network)) {
|
||||||
let transportPath = proxy[`${proxy.network}-opts`]?.path;
|
let transportPath = proxy[`${proxy.network}-opts`]?.path;
|
||||||
if (!transportPath) {
|
if (!transportPath) {
|
||||||
@@ -328,6 +422,43 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof proxy.name !== 'string') {
|
||||||
|
if (/^\d+$/.test(proxy.name)) {
|
||||||
|
proxy.name = `${proxy.name}`;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (proxy.name?.data) {
|
||||||
|
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
||||||
|
} else {
|
||||||
|
proxy.name = utf8ArrayToStr(proxy.name);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`proxy.name decode failed\nReason: ${e}`);
|
||||||
|
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (['ws', 'http', 'h2'].includes(proxy.network)) {
|
||||||
|
if (
|
||||||
|
['ws', 'h2'].includes(proxy.network) &&
|
||||||
|
!proxy[`${proxy.network}-opts`]?.path
|
||||||
|
) {
|
||||||
|
proxy[`${proxy.network}-opts`] =
|
||||||
|
proxy[`${proxy.network}-opts`] || {};
|
||||||
|
proxy[`${proxy.network}-opts`].path = '/';
|
||||||
|
} else if (
|
||||||
|
proxy.network === 'http' &&
|
||||||
|
(!Array.isArray(proxy[`${proxy.network}-opts`]?.path) ||
|
||||||
|
proxy[`${proxy.network}-opts`]?.path.every((i) => !i))
|
||||||
|
) {
|
||||||
|
proxy[`${proxy.network}-opts`] =
|
||||||
|
proxy[`${proxy.network}-opts`] || {};
|
||||||
|
proxy[`${proxy.network}-opts`].path = ['/'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (['', 'off'].includes(proxy.sni)) {
|
||||||
|
proxy['disable-sni'] = true;
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Base64 } from 'js-base64';
|
|||||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
function URI_SS() {
|
function URI_SS() {
|
||||||
|
// TODO: 暂不支持 httpupgrade
|
||||||
const name = 'URI SS Parser';
|
const name = 'URI SS Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
return /^ss:\/\//.test(line);
|
return /^ss:\/\//.test(line);
|
||||||
@@ -63,9 +64,9 @@ function URI_SS() {
|
|||||||
/\d+/,
|
/\d+/,
|
||||||
)?.[0];
|
)?.[0];
|
||||||
|
|
||||||
const userInfo = userInfoStr.split(':');
|
const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
||||||
proxy.cipher = userInfo[0];
|
proxy.cipher = userInfo[1];
|
||||||
proxy.password = userInfo[1];
|
proxy.password = userInfo[2];
|
||||||
|
|
||||||
// handle obfs
|
// handle obfs
|
||||||
const idx = content.indexOf('?plugin=');
|
const idx = content.indexOf('?plugin=');
|
||||||
@@ -273,11 +274,17 @@ function URI_VMess() {
|
|||||||
params.port = port;
|
params.port = port;
|
||||||
params.add = server;
|
params.add = server;
|
||||||
}
|
}
|
||||||
|
const server = params.add;
|
||||||
|
const port = parseInt(getIfPresent(params.port), 10);
|
||||||
const proxy = {
|
const proxy = {
|
||||||
name: params.ps ?? params.remarks,
|
name:
|
||||||
|
params.ps ??
|
||||||
|
params.remarks ??
|
||||||
|
params.remark ??
|
||||||
|
`VMess ${server}:${port}`,
|
||||||
type: 'vmess',
|
type: 'vmess',
|
||||||
server: params.add,
|
server,
|
||||||
port: parseInt(getIfPresent(params.port), 10),
|
port,
|
||||||
cipher: getIfPresent(params.scy, 'auto'),
|
cipher: getIfPresent(params.scy, 'auto'),
|
||||||
uuid: params.id,
|
uuid: params.id,
|
||||||
alterId: parseInt(
|
alterId: parseInt(
|
||||||
@@ -293,6 +300,7 @@ function URI_VMess() {
|
|||||||
if (proxy.tls && proxy.sni) {
|
if (proxy.tls && proxy.sni) {
|
||||||
proxy.sni = params.sni;
|
proxy.sni = params.sni;
|
||||||
}
|
}
|
||||||
|
let httpupgrade = false;
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (params.net === 'ws' || params.obfs === 'websocket') {
|
if (params.net === 'ws' || params.obfs === 'websocket') {
|
||||||
proxy.network = 'ws';
|
proxy.network = 'ws';
|
||||||
@@ -303,6 +311,12 @@ function URI_VMess() {
|
|||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
} else if (['grpc'].includes(params.net)) {
|
} else if (['grpc'].includes(params.net)) {
|
||||||
proxy.network = 'grpc';
|
proxy.network = 'grpc';
|
||||||
|
} else if (
|
||||||
|
params.net === 'httpupgrade' ||
|
||||||
|
proxy.network === 'httpupgrade'
|
||||||
|
) {
|
||||||
|
proxy.network = 'ws';
|
||||||
|
httpupgrade = true;
|
||||||
}
|
}
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
let transportHost = params.host ?? params.obfsParam;
|
let transportHost = params.host ?? params.obfsParam;
|
||||||
@@ -326,6 +340,8 @@ function URI_VMess() {
|
|||||||
transportPath = Array.isArray(transportPath)
|
transportPath = Array.isArray(transportPath)
|
||||||
? transportPath[0]
|
? transportPath[0]
|
||||||
: transportPath;
|
: transportPath;
|
||||||
|
} else {
|
||||||
|
transportPath = '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (transportPath || transportHost) {
|
if (transportPath || transportHost) {
|
||||||
@@ -335,10 +351,15 @@ function URI_VMess() {
|
|||||||
'_grpc-type': getIfNotBlank(params.type),
|
'_grpc-type': getIfNotBlank(params.type),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
proxy[`${proxy.network}-opts`] = {
|
const opts = {
|
||||||
path: getIfNotBlank(transportPath),
|
path: getIfNotBlank(transportPath),
|
||||||
headers: { Host: getIfNotBlank(transportHost) },
|
headers: { Host: getIfNotBlank(transportHost) },
|
||||||
};
|
};
|
||||||
|
if (httpupgrade) {
|
||||||
|
opts['v2ray-http-upgrade'] = true;
|
||||||
|
opts['v2ray-http-upgrade-fast-open'] = true;
|
||||||
|
}
|
||||||
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete proxy.network;
|
delete proxy.network;
|
||||||
@@ -399,14 +420,18 @@ function URI_VLESS() {
|
|||||||
params[key] = value;
|
params[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy.name = name ?? params.remarks ?? `VLESS ${server}:${port}`;
|
proxy.name =
|
||||||
|
name ??
|
||||||
|
params.remarks ??
|
||||||
|
params.remark ??
|
||||||
|
`VLESS ${server}:${port}`;
|
||||||
|
|
||||||
proxy.tls = params.security && params.security !== 'none';
|
proxy.tls = params.security && params.security !== 'none';
|
||||||
if (isShadowrocket && /TRUE|1/i.test(params.tls)) {
|
if (isShadowrocket && /TRUE|1/i.test(params.tls)) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
params.security = params.security ?? 'reality';
|
params.security = params.security ?? 'reality';
|
||||||
}
|
}
|
||||||
proxy.sni = params.sni ?? params.peer;
|
proxy.sni = params.sni || params.peer;
|
||||||
proxy.flow = params.flow;
|
proxy.flow = params.flow;
|
||||||
if (!proxy.flow && isShadowrocket && params.xtls) {
|
if (!proxy.flow && isShadowrocket && params.xtls) {
|
||||||
// "none" is undefined
|
// "none" is undefined
|
||||||
@@ -434,9 +459,13 @@ function URI_VLESS() {
|
|||||||
proxy[`${params.security}-opts`] = opts;
|
proxy[`${params.security}-opts`] = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let httpupgrade = false;
|
||||||
proxy.network = params.type;
|
proxy.network = params.type;
|
||||||
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
if (proxy.network === 'tcp' && params.headerType === 'http') {
|
||||||
proxy.network = 'http';
|
proxy.network = 'http';
|
||||||
|
} else if (proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws';
|
||||||
|
httpupgrade = true;
|
||||||
}
|
}
|
||||||
if (!proxy.network && isShadowrocket && params.obfs) {
|
if (!proxy.network && isShadowrocket && params.obfs) {
|
||||||
proxy.network = params.obfs;
|
proxy.network = params.obfs;
|
||||||
@@ -474,6 +503,10 @@ function URI_VLESS() {
|
|||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
opts['_grpc-type'] = params.mode || 'gun';
|
opts['_grpc-type'] = params.mode || 'gun';
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
opts['v2ray-http-upgrade'] = true;
|
||||||
|
opts['v2ray-http-upgrade-fast-open'] = true;
|
||||||
|
}
|
||||||
if (Object.keys(opts).length > 0) {
|
if (Object.keys(opts).length > 0) {
|
||||||
proxy[`${proxy.network}-opts`] = opts;
|
proxy[`${proxy.network}-opts`] = opts;
|
||||||
}
|
}
|
||||||
@@ -536,6 +569,7 @@ function URI_Hysteria2() {
|
|||||||
proxy.obfs = params.obfs;
|
proxy.obfs = params.obfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy.ports = params.mport;
|
||||||
proxy['obfs-password'] = params['obfs-password'];
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
@@ -662,6 +696,89 @@ function URI_TUIC() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_WireGuard() {
|
||||||
|
const name = 'URI WireGuard Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^(wireguard|wg):\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
line = line.split(/(wireguard|wg):\/\//)[2];
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
let [
|
||||||
|
__,
|
||||||
|
___,
|
||||||
|
privateKey,
|
||||||
|
server,
|
||||||
|
____,
|
||||||
|
port,
|
||||||
|
_____,
|
||||||
|
addons = '',
|
||||||
|
name,
|
||||||
|
] = /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
|
port = parseInt(`${port}`, 10);
|
||||||
|
if (isNaN(port)) {
|
||||||
|
port = 51820;
|
||||||
|
}
|
||||||
|
privateKey = decodeURIComponent(privateKey);
|
||||||
|
if (name != null) {
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
}
|
||||||
|
name = name ?? `WireGuard ${server}:${port}`;
|
||||||
|
const proxy = {
|
||||||
|
type: 'wireguard',
|
||||||
|
name,
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
'private-key': privateKey,
|
||||||
|
udp: true,
|
||||||
|
};
|
||||||
|
for (const addon of addons.split('&')) {
|
||||||
|
let [key, value] = addon.split('=');
|
||||||
|
key = key.replace(/_/, '-');
|
||||||
|
value = decodeURIComponent(value);
|
||||||
|
if (['reserved'].includes(key)) {
|
||||||
|
const parsed = value
|
||||||
|
.split(',')
|
||||||
|
.map((i) => parseInt(i.trim(), 10))
|
||||||
|
.filter((i) => Number.isInteger(i));
|
||||||
|
if (parsed.length === 3) {
|
||||||
|
proxy[key] = parsed;
|
||||||
|
}
|
||||||
|
} else if (['address', 'ip'].includes(key)) {
|
||||||
|
value.split(',').map((i) => {
|
||||||
|
const ip = i
|
||||||
|
.trim()
|
||||||
|
.replace(/\/\d+$/, '')
|
||||||
|
.replace(/^\[/, '')
|
||||||
|
.replace(/\]$/, '');
|
||||||
|
if (isIPv4(ip)) {
|
||||||
|
proxy.ip = ip;
|
||||||
|
} else if (isIPv6(ip)) {
|
||||||
|
proxy.ipv6 = ip;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (['mtu'].includes(key)) {
|
||||||
|
const parsed = parseInt(value.trim(), 10);
|
||||||
|
if (Number.isInteger(parsed)) {
|
||||||
|
proxy[key] = parsed;
|
||||||
|
}
|
||||||
|
} else if (/publickey/i.test(key)) {
|
||||||
|
proxy['public-key'] = value;
|
||||||
|
} else if (/privatekey/i.test(key)) {
|
||||||
|
proxy['private-key'] = value;
|
||||||
|
} else if (['udp'].includes(key)) {
|
||||||
|
proxy[key] = /(TRUE)|1/i.test(value);
|
||||||
|
} else if (!['flag'].includes(key)) {
|
||||||
|
proxy[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
// Trojan URI format
|
// Trojan URI format
|
||||||
function URI_Trojan() {
|
function URI_Trojan() {
|
||||||
@@ -712,6 +829,7 @@ function Clash_All() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'wireguard',
|
'wireguard',
|
||||||
|
'ssh',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -735,13 +853,22 @@ function Clash_All() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proxy['server-cert-fingerprint']) {
|
||||||
|
proxy['tls-fingerprint'] = proxy['server-cert-fingerprint'];
|
||||||
|
}
|
||||||
if (proxy.fingerprint) {
|
if (proxy.fingerprint) {
|
||||||
proxy['tls-fingerprint'] = proxy.fingerprint;
|
proxy['tls-fingerprint'] = proxy.fingerprint;
|
||||||
}
|
}
|
||||||
|
if (proxy['dialer-proxy']) {
|
||||||
|
proxy['underlying-proxy'] = proxy['dialer-proxy'];
|
||||||
|
}
|
||||||
|
|
||||||
if (proxy['benchmark-url']) {
|
if (proxy['benchmark-url']) {
|
||||||
proxy['test-url'] = proxy['benchmark-url'];
|
proxy['test-url'] = proxy['benchmark-url'];
|
||||||
}
|
}
|
||||||
|
if (proxy['benchmark-timeout']) {
|
||||||
|
proxy['test-timeout'] = proxy['benchmark-timeout'];
|
||||||
|
}
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
@@ -894,6 +1021,15 @@ function Loon_Http() {
|
|||||||
const parse = (line) => getLoonParser().parse(line);
|
const parse = (line) => getLoonParser().parse(line);
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function Loon_Socks5() {
|
||||||
|
const name = 'Loon SOCKS5 Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*socks5/i.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parse = (line) => getLoonParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
function Loon_WireGuard() {
|
function Loon_WireGuard() {
|
||||||
const name = 'Loon WireGuard Parser';
|
const name = 'Loon WireGuard Parser';
|
||||||
@@ -1003,6 +1139,14 @@ function Loon_WireGuard() {
|
|||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Surge_SSH() {
|
||||||
|
const name = 'Surge SSH Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^.*=\s*ssh/.test(line.split(',')[0]);
|
||||||
|
};
|
||||||
|
const parse = (line) => getSurgeParser().parse(line);
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
function Surge_SS() {
|
function Surge_SS() {
|
||||||
const name = 'Surge SS Parser';
|
const name = 'Surge SS Parser';
|
||||||
const test = (line) => {
|
const test = (line) => {
|
||||||
@@ -1169,10 +1313,12 @@ export default [
|
|||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
URI_VLESS(),
|
URI_VLESS(),
|
||||||
URI_TUIC(),
|
URI_TUIC(),
|
||||||
|
URI_WireGuard(),
|
||||||
URI_Hysteria(),
|
URI_Hysteria(),
|
||||||
URI_Hysteria2(),
|
URI_Hysteria2(),
|
||||||
URI_Trojan(),
|
URI_Trojan(),
|
||||||
Clash_All(),
|
Clash_All(),
|
||||||
|
Surge_SSH(),
|
||||||
Surge_SS(),
|
Surge_SS(),
|
||||||
Surge_VMess(),
|
Surge_VMess(),
|
||||||
Surge_Trojan(),
|
Surge_Trojan(),
|
||||||
@@ -1190,6 +1336,7 @@ export default [
|
|||||||
Loon_Hysteria2(),
|
Loon_Hysteria2(),
|
||||||
Loon_Trojan(),
|
Loon_Trojan(),
|
||||||
Loon_Http(),
|
Loon_Http(),
|
||||||
|
Loon_Socks5(),
|
||||||
Loon_WireGuard(),
|
Loon_WireGuard(),
|
||||||
QX_SS(),
|
QX_SS(),
|
||||||
QX_SSR(),
|
QX_SSR(),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +78,9 @@ https = tag equals "https"i address (username password)? (tls_host/tls_verificat
|
|||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
|
proxy.type = "socks5";
|
||||||
|
}
|
||||||
|
|
||||||
address = comma server:server comma port:port {
|
address = comma server:server comma port:port {
|
||||||
proxy.server = server;
|
proxy.server = server;
|
||||||
@@ -167,7 +170,7 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma "tls-name" equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +76,9 @@ https = tag equals "https"i address (username password)? (tls_host/tls_verificat
|
|||||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
}
|
}
|
||||||
|
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||||
|
proxy.type = "socks5";
|
||||||
|
}
|
||||||
|
|
||||||
address = comma server:server comma port:port {
|
address = comma server:server comma port:port {
|
||||||
proxy.server = server;
|
proxy.server = server;
|
||||||
@@ -165,7 +168,7 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
|||||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||||
|
|
||||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||||
tls_host = comma "tls-name" equals host:domain { proxy.sni = host; }
|
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
|
|
||||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||||
|
|||||||
@@ -50,8 +50,11 @@ trojan = "trojan" equals address
|
|||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol) {
|
if (proxy.protocol || proxy.type === "ssr") {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
|
if (!proxy.protocol) {
|
||||||
|
proxy.protocol = "origin";
|
||||||
|
}
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
||||||
if (obfs.type) proxy.obfs = obfs.type;
|
if (obfs.type) proxy.obfs = obfs.type;
|
||||||
@@ -172,7 +175,7 @@ tls_no_session_reuse = comma "tls-no-session-reuse" equals flag:bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
||||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { proxy.type = "ssr"; obfs.type = type; return type; }
|
||||||
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
||||||
|
|
||||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||||
|
|||||||
@@ -48,8 +48,11 @@ trojan = "trojan" equals address
|
|||||||
|
|
||||||
shadowsocks = "shadowsocks" equals address
|
shadowsocks = "shadowsocks" equals address
|
||||||
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
(password/method/obfs_ssr/obfs_ss/obfs_host/obfs_uri/ssr_protocol/ssr_protocol_param/tls_pubkey_sha256/tls_alpn/tls_no_session_ticket/tls_no_session_reuse/tls_fingerprint/tls_verification/udp_relay/udp_over_tcp/fast_open/tag/server_check_url/others)* {
|
||||||
if (proxy.protocol) {
|
if (proxy.protocol || proxy.type === "ssr") {
|
||||||
proxy.type = "ssr";
|
proxy.type = "ssr";
|
||||||
|
if (!proxy.protocol) {
|
||||||
|
proxy.protocol = "origin";
|
||||||
|
}
|
||||||
// handle ssr obfs
|
// handle ssr obfs
|
||||||
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
if (obfs.host) proxy["obfs-param"] = obfs.host;
|
||||||
if (obfs.type) proxy.obfs = obfs.type;
|
if (obfs.type) proxy.obfs = obfs.type;
|
||||||
@@ -170,7 +173,7 @@ tls_no_session_reuse = comma "tls-no-session-reuse" equals flag:bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
obfs_ss = comma "obfs" equals type:("http"/"tls"/"wss"/"ws"/"over-tls") { obfs.type = type; return type; }
|
||||||
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { obfs.type = type; return type; }
|
obfs_ssr = comma "obfs" equals type:("plain"/"http_simple"/"http_post"/"random_head"/"tls1.2_ticket_auth"/"tls1.2_ticket_fastauth") { proxy.type = "ssr"; obfs.type = type; return type; }
|
||||||
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
obfs = comma "obfs" equals type:("wss"/"ws"/"over-tls"/"http") { obfs.type = type; return type; };
|
||||||
|
|
||||||
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; }
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ const grammars = String.raw`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -52,7 +52,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -63,21 +63,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
|
proxy.type = "ssh";
|
||||||
|
handleShadowTLS();
|
||||||
|
}
|
||||||
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -87,28 +91,28 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
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)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/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)? (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.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
@@ -173,7 +177,13 @@ username = & {
|
|||||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||||
|
|
||||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
sni = comma "sni" equals sni:("off"/domain) {
|
||||||
|
if (sni === "off") {
|
||||||
|
proxy["disable-sni"] = true;
|
||||||
|
} else {
|
||||||
|
proxy.sni = sni;
|
||||||
|
}
|
||||||
|
}
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
|
||||||
@@ -218,6 +228,15 @@ no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-ale
|
|||||||
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
|
test_udp = comma "test-udp" equals match:[^,]+ { proxy["test-udp"] = match.join(""); }
|
||||||
|
test_timeout = comma "test-timeout" equals match:$[0-9]+ { proxy["test-timeout"] = parseInt(match.trim()); }
|
||||||
|
tos = comma "tos" equals match:$[0-9]+ { proxy.tos = parseInt(match.trim()); }
|
||||||
|
interface = comma "interface" equals match:[^,]+ { proxy.interface = match.join(""); }
|
||||||
|
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
||||||
|
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
||||||
|
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
||||||
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2) {
|
start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5/wireguard/hysteria2/ssh) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "ss";
|
proxy.type = "ss";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -50,7 +50,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "vmess";
|
proxy.type = "vmess";
|
||||||
proxy.cipher = proxy.cipher || "none";
|
proxy.cipher = proxy.cipher || "none";
|
||||||
if (proxy.aead) {
|
if (proxy.aead) {
|
||||||
@@ -61,21 +61,25 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
|
|||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
handleWebsocket();
|
handleWebsocket();
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
https = tag equals "https" address (username password)? (usernamek passwordk)? (sni/tls_fingerprint/tls_verification/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
http = tag equals "http" address (username password)? (usernamek passwordk)? (ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "http";
|
proxy.type = "http";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (server_fingerprint/idle_timeout/private_key/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
|
proxy.type = "ssh";
|
||||||
|
handleShadowTLS();
|
||||||
|
}
|
||||||
|
snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "snell";
|
proxy.type = "snell";
|
||||||
// handle obfs
|
// handle obfs
|
||||||
if (obfs.type == "http" || obfs.type === "tls") {
|
if (obfs.type == "http" || obfs.type === "tls") {
|
||||||
@@ -85,28 +89,28 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
|
|||||||
}
|
}
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/no_error_alert/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "tuic";
|
proxy.type = "tuic";
|
||||||
proxy.version = 5;
|
proxy.version = 5;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/test_url/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "wireguard-surge";
|
proxy.type = "wireguard-surge";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/test_url/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||||
proxy.type = "hysteria2";
|
proxy.type = "hysteria2";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/fast_open/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
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)* {
|
||||||
proxy.type = "socks5";
|
proxy.type = "socks5";
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
}
|
}
|
||||||
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (no_error_alert/ip_version/underlying_proxy/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)? (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.type = "socks5";
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
handleShadowTLS();
|
handleShadowTLS();
|
||||||
@@ -171,7 +175,13 @@ username = & {
|
|||||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||||
|
|
||||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
sni = comma "sni" equals sni:("off"/domain) {
|
||||||
|
if (sni === "off") {
|
||||||
|
proxy["disable-sni"] = true;
|
||||||
|
} else {
|
||||||
|
proxy.sni = sni;
|
||||||
|
}
|
||||||
|
}
|
||||||
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
|
||||||
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
|
||||||
@@ -216,6 +226,15 @@ no_error_alert = comma "no-error-alert" equals match:[^,]+ { proxy["no-error-ale
|
|||||||
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
underlying_proxy = comma "underlying-proxy" equals match:[^,]+ { proxy["underlying-proxy"] = match.join(""); }
|
||||||
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down = match.join(""); }
|
||||||
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
|
||||||
|
test_udp = comma "test-udp" equals match:[^,]+ { proxy["test-udp"] = match.join(""); }
|
||||||
|
test_timeout = comma "test-timeout" equals match:$[0-9]+ { proxy["test-timeout"] = parseInt(match.trim()); }
|
||||||
|
tos = comma "tos" equals match:$[0-9]+ { proxy.tos = parseInt(match.trim()); }
|
||||||
|
interface = comma "interface" equals match:[^,]+ { proxy.interface = match.join(""); }
|
||||||
|
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }
|
||||||
|
hybrid = comma "hybrid" equals flag:bool { proxy.hybrid = flag; }
|
||||||
|
idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"] = parseInt(match.trim()); }
|
||||||
|
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
|
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||||
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
|
||||||
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ start = (trojan) {
|
|||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
|
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
proxy.password = password;
|
proxy.password = password;
|
||||||
proxy.server = server;
|
proxy.server = server;
|
||||||
@@ -79,7 +79,7 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
params = "/"? "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
|
|
||||||
@@ -89,7 +89,12 @@ params = "/"? "?" head:param tail:("&"@param)* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params["type"]) {
|
if (params["type"]) {
|
||||||
|
let httpupgrade
|
||||||
proxy.network = params["type"]
|
proxy.network = params["type"]
|
||||||
|
if(proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws'
|
||||||
|
httpupgrade = true
|
||||||
|
}
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
proxy[proxy.network + '-opts'] = {
|
proxy[proxy.network + '-opts'] = {
|
||||||
'grpc-service-name': params["serviceName"],
|
'grpc-service-name': params["serviceName"],
|
||||||
@@ -102,6 +107,10 @@ params = "/"? "?" head:param tail:("&"@param)* {
|
|||||||
if (params["host"]) {
|
if (params["host"]) {
|
||||||
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ start = (trojan) {
|
|||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
|
trojan = "trojan://" password:password "@" server:server ":" port:port "/"? params? name:name?{
|
||||||
proxy.type = "trojan";
|
proxy.type = "trojan";
|
||||||
proxy.password = password;
|
proxy.password = password;
|
||||||
proxy.server = server;
|
proxy.server = server;
|
||||||
@@ -77,7 +77,7 @@ port = digits:[0-9]+ {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
params = "/"? "?" head:param tail:("&"@param)* {
|
params = "?" head:param tail:("&"@param)* {
|
||||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||||
proxy.sni = params["sni"] || params["peer"];
|
proxy.sni = params["sni"] || params["peer"];
|
||||||
|
|
||||||
@@ -87,7 +87,12 @@ params = "/"? "?" head:param tail:("&"@param)* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params["type"]) {
|
if (params["type"]) {
|
||||||
|
let httpupgrade
|
||||||
proxy.network = params["type"]
|
proxy.network = params["type"]
|
||||||
|
if(proxy.network === 'httpupgrade') {
|
||||||
|
proxy.network = 'ws'
|
||||||
|
httpupgrade = true
|
||||||
|
}
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
proxy[proxy.network + '-opts'] = {
|
proxy[proxy.network + '-opts'] = {
|
||||||
'grpc-service-name': params["serviceName"],
|
'grpc-service-name': params["serviceName"],
|
||||||
@@ -100,6 +105,10 @@ params = "/"? "?" head:param tail:("&"@param)* {
|
|||||||
if (params["host"]) {
|
if (params["host"]) {
|
||||||
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
$set(proxy, proxy.network+"-opts.headers.Host", decodeURIComponent(params["host"]));
|
||||||
}
|
}
|
||||||
|
if (httpupgrade) {
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade", true);
|
||||||
|
$set(proxy, proxy.network+"-opts.v2ray-http-upgrade-fast-open", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ function Base64Encoded() {
|
|||||||
'aHR0c', // htt
|
'aHR0c', // htt
|
||||||
'dmxlc3M=', // vless
|
'dmxlc3M=', // vless
|
||||||
'aHlzdGVyaWEy', // hysteria2
|
'aHlzdGVyaWEy', // hysteria2
|
||||||
|
'aHkyOi8v', // hy2://
|
||||||
|
'd2lyZWd1YXJkOi8v', // wireguard://
|
||||||
|
'd2c6Ly8=', // wg://
|
||||||
|
'dHVpYzovLw==', // tuic://
|
||||||
];
|
];
|
||||||
|
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import resourceCache from '@/utils/resource-cache';
|
|||||||
import scriptResourceCache from '@/utils/script-resource-cache';
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
import { isIPv4, isIPv6 } from '@/utils';
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
import { FULL } from '@/utils/logical';
|
import { FULL } from '@/utils/logical';
|
||||||
import { getFlag } from '@/utils/geo';
|
import { getFlag, removeFlag } from '@/utils/geo';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
validCheck,
|
validCheck,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
|
getRmainingDays,
|
||||||
} from '@/utils/flow';
|
} from '@/utils/flow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +88,9 @@ function QuickSettingOperator(args) {
|
|||||||
if (get(args.useless)) {
|
if (get(args.useless)) {
|
||||||
const filter = UselessFilter();
|
const filter = UselessFilter();
|
||||||
const selected = filter.func(proxies);
|
const selected = filter.func(proxies);
|
||||||
proxies.filter((_, i) => selected[i]);
|
proxies = proxies.filter(
|
||||||
|
(p, i) => selected[i] && p.port > 0 && p.port <= 65535,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
@@ -119,7 +122,7 @@ function QuickSettingOperator(args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add or remove flag for proxies
|
// add or remove flag for proxies
|
||||||
function FlagOperator({ mode }) {
|
function FlagOperator({ mode, tw }) {
|
||||||
return {
|
return {
|
||||||
name: 'Flag Operator',
|
name: 'Flag Operator',
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
@@ -133,7 +136,13 @@ function FlagOperator({ mode }) {
|
|||||||
// remove old flag
|
// remove old flag
|
||||||
proxy.name = removeFlag(proxy.name);
|
proxy.name = removeFlag(proxy.name);
|
||||||
proxy.name = newFlag + ' ' + proxy.name;
|
proxy.name = newFlag + ' ' + proxy.name;
|
||||||
proxy.name = proxy.name.replace(/🇹🇼/g, '🇨🇳');
|
if (tw == 'ws') {
|
||||||
|
proxy.name = proxy.name.replace(/🇹🇼/g, '🇼🇸');
|
||||||
|
} else if (tw == 'tw') {
|
||||||
|
// 不变
|
||||||
|
} else {
|
||||||
|
proxy.name = proxy.name.replace(/🇹🇼/g, '🇨🇳');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
});
|
});
|
||||||
@@ -349,11 +358,41 @@ function ScriptOperator(script, targetPlatform, $arguments, source) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseIP4P(IP4P) {
|
||||||
|
let server;
|
||||||
|
let port;
|
||||||
|
try {
|
||||||
|
if (!/^2001::[^:]+:[^:]+:[^:]+$/.test(IP4P)) {
|
||||||
|
throw new Error(`Invalid IP4P: ${IP4P}`);
|
||||||
|
}
|
||||||
|
let array = IP4P.split(':');
|
||||||
|
|
||||||
|
port = parseInt(array[2], 16);
|
||||||
|
let ipab = parseInt(array[3], 16);
|
||||||
|
let ipcd = parseInt(array[4], 16);
|
||||||
|
let ipa = ipab >> 8;
|
||||||
|
let ipb = ipab & 0xff;
|
||||||
|
let ipc = ipcd >> 8;
|
||||||
|
let ipd = ipcd & 0xff;
|
||||||
|
server = `${ipa}.${ipb}.${ipc}.${ipd}`;
|
||||||
|
if (port <= 0 || port > 65535) {
|
||||||
|
throw new Error(`Invalid port number: ${port}`);
|
||||||
|
}
|
||||||
|
if (!isIPv4(server)) {
|
||||||
|
throw new Error(`Invalid IP address: ${server}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// throw new Error(`IP4P 解析失败: ${e}`);
|
||||||
|
$.error(`IP4P 解析失败: ${e}`);
|
||||||
|
}
|
||||||
|
return { server, port };
|
||||||
|
}
|
||||||
|
|
||||||
const DOMAIN_RESOLVERS = {
|
const DOMAIN_RESOLVERS = {
|
||||||
Google: async function (domain, type) {
|
Google: async function (domain, type, noCache) {
|
||||||
const id = hex_md5(`GOOGLE:${domain}:${type}`);
|
const id = hex_md5(`GOOGLE:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
|
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
@@ -374,10 +413,13 @@ const DOMAIN_RESOLVERS = {
|
|||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
'IP-API': async function (domain) {
|
'IP-API': async function (domain, type, noCache) {
|
||||||
|
if (['IPv6'].includes(type)) {
|
||||||
|
throw new Error(`域名解析服务提供方 IP-API 不支持 ${type}`);
|
||||||
|
}
|
||||||
const id = hex_md5(`IP-API:${domain}`);
|
const id = hex_md5(`IP-API:${domain}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `http://ip-api.com/json/${encodeURIComponent(
|
url: `http://ip-api.com/json/${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
@@ -391,10 +433,10 @@ const DOMAIN_RESOLVERS = {
|
|||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Cloudflare: async function (domain, type) {
|
Cloudflare: async function (domain, type, noCache) {
|
||||||
const id = hex_md5(`CLOUDFLARE:${domain}:${type}`);
|
const id = hex_md5(`CLOUDFLARE:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `https://1.0.0.1/dns-query?name=${encodeURIComponent(
|
url: `https://1.0.0.1/dns-query?name=${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
@@ -415,12 +457,12 @@ const DOMAIN_RESOLVERS = {
|
|||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Ali: async function (domain, type) {
|
Ali: async function (domain, type, noCache) {
|
||||||
const id = hex_md5(`ALI:${domain}:${type}`);
|
const id = hex_md5(`ALI:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `http://223.6.6.6/resolve?name=${encodeURIComponent(
|
url: `http://223.6.6.6/resolve?edns_client_subnet=223.6.6.6/24&name=${encodeURIComponent(
|
||||||
domain,
|
domain,
|
||||||
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
|
)}&type=${type === 'IPv6' ? 'AAAA' : 'A'}&short=1`,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -435,12 +477,12 @@ const DOMAIN_RESOLVERS = {
|
|||||||
resourceCache.set(id, result);
|
resourceCache.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
Tencent: async function (domain, type) {
|
Tencent: async function (domain, type, noCache) {
|
||||||
const id = hex_md5(`ALI:${domain}:${type}`);
|
const id = hex_md5(`ALI:${domain}:${type}`);
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (cached) return cached;
|
if (!noCache && cached) return cached;
|
||||||
const resp = await $.http.get({
|
const resp = await $.http.get({
|
||||||
url: `http://119.28.28.28/d?type=${
|
url: `http://119.28.28.28/d?ip=119.28.28.28&type=${
|
||||||
type === 'IPv6' ? 'AAAA' : 'A'
|
type === 'IPv6' ? 'AAAA' : 'A'
|
||||||
}&dn=${encodeURIComponent(domain)}`,
|
}&dn=${encodeURIComponent(domain)}`,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -448,7 +490,7 @@ const DOMAIN_RESOLVERS = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
||||||
if (answers.length === 0) {
|
if (answers.length === 0 || String(answers) === '0') {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1];
|
const result = answers[answers.length - 1];
|
||||||
@@ -457,10 +499,12 @@ const DOMAIN_RESOLVERS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function ResolveDomainOperator({ provider, type, filter }) {
|
function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
||||||
if (type === 'IPv6' && ['IP-API'].includes(provider)) {
|
if (['IPv6', 'IP4P'].includes(_type) && ['IP-API'].includes(provider)) {
|
||||||
throw new Error(`域名解析服务提供方 ${provider} 不支持 IPv6`);
|
throw new Error(`域名解析服务提供方 ${provider} 不支持 ${_type}`);
|
||||||
}
|
}
|
||||||
|
let type = ['IPv6', 'IP4P'].includes(_type) ? 'IPv6' : 'IPv4';
|
||||||
|
|
||||||
const resolver = DOMAIN_RESOLVERS[provider];
|
const resolver = DOMAIN_RESOLVERS[provider];
|
||||||
if (!resolver) {
|
if (!resolver) {
|
||||||
throw new Error(`找不到域名解析服务提供方: ${provider}`);
|
throw new Error(`找不到域名解析服务提供方: ${provider}`);
|
||||||
@@ -468,12 +512,17 @@ function ResolveDomainOperator({ provider, type, filter }) {
|
|||||||
return {
|
return {
|
||||||
name: 'Resolve Domain Operator',
|
name: 'Resolve Domain Operator',
|
||||||
func: async (proxies) => {
|
func: async (proxies) => {
|
||||||
|
proxies.forEach((p, i) => {
|
||||||
|
if (!p['_no-resolve'] && p['no-resolve']) {
|
||||||
|
proxies[i]['_no-resolve'] = p['no-resolve'];
|
||||||
|
}
|
||||||
|
});
|
||||||
const results = {};
|
const results = {};
|
||||||
const limit = 15; // more than 20 concurrency may result in surge TCP connection shortage.
|
const limit = 15; // more than 20 concurrency may result in surge TCP connection shortage.
|
||||||
const totalDomain = [
|
const totalDomain = [
|
||||||
...new Set(
|
...new Set(
|
||||||
proxies
|
proxies
|
||||||
.filter((p) => !isIP(p.server) && !p['no-resolve'])
|
.filter((p) => !isIP(p.server) && !p['_no-resolve'])
|
||||||
.map((c) => c.server),
|
.map((c) => c.server),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -482,7 +531,7 @@ function ResolveDomainOperator({ provider, type, filter }) {
|
|||||||
const currentBatch = [];
|
const currentBatch = [];
|
||||||
for (let domain of totalDomain.splice(0, limit)) {
|
for (let domain of totalDomain.splice(0, limit)) {
|
||||||
currentBatch.push(
|
currentBatch.push(
|
||||||
resolver(domain, type)
|
resolver(domain, type, cache === 'disabled')
|
||||||
.then((ip) => {
|
.then((ip) => {
|
||||||
results[domain] = ip;
|
results[domain] = ip;
|
||||||
$.info(
|
$.info(
|
||||||
@@ -499,10 +548,33 @@ function ResolveDomainOperator({ provider, type, filter }) {
|
|||||||
await Promise.all(currentBatch);
|
await Promise.all(currentBatch);
|
||||||
}
|
}
|
||||||
proxies.forEach((p) => {
|
proxies.forEach((p) => {
|
||||||
if (!p['no-resolve']) {
|
if (!p['_no-resolve']) {
|
||||||
if (results[p.server]) {
|
if (results[p.server]) {
|
||||||
p.server = results[p.server];
|
if (_type === 'IP4P') {
|
||||||
p.resolved = true;
|
const { server, port } = parseIP4P(
|
||||||
|
results[p.server],
|
||||||
|
);
|
||||||
|
if (server && port) {
|
||||||
|
p._domain = p.server;
|
||||||
|
p.server = server;
|
||||||
|
p.port = port;
|
||||||
|
p.resolved = true;
|
||||||
|
p._IPv4 = p.server;
|
||||||
|
if (!isIP(p._IP)) {
|
||||||
|
p._IP = p.server;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.resolved = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p._domain = p.server;
|
||||||
|
p.server = results[p.server];
|
||||||
|
p.resolved = true;
|
||||||
|
p[`_${type}`] = p.server;
|
||||||
|
if (!isIP(p._IP)) {
|
||||||
|
p._IP = p.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.resolved = false;
|
p.resolved = false;
|
||||||
}
|
}
|
||||||
@@ -511,7 +583,7 @@ function ResolveDomainOperator({ provider, type, filter }) {
|
|||||||
|
|
||||||
return proxies.filter((p) => {
|
return proxies.filter((p) => {
|
||||||
if (filter === 'removeFailed') {
|
if (filter === 'removeFailed') {
|
||||||
return p['no-resolve'] || p.resolved;
|
return isIP(p.server) || p['_no-resolve'] || p.resolved;
|
||||||
} else if (filter === 'IPOnly') {
|
} else if (filter === 'IPOnly') {
|
||||||
return isIP(p.server);
|
return isIP(p.server);
|
||||||
} else if (filter === 'IPv4Only') {
|
} else if (filter === 'IPv4Only') {
|
||||||
@@ -562,6 +634,8 @@ function RegionFilter(regions) {
|
|||||||
SG: '🇸🇬',
|
SG: '🇸🇬',
|
||||||
JP: '🇯🇵',
|
JP: '🇯🇵',
|
||||||
UK: '🇬🇧',
|
UK: '🇬🇧',
|
||||||
|
DE: '🇩🇪',
|
||||||
|
KR: '🇰🇷',
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
name: 'Region Filter',
|
name: 'Region Filter',
|
||||||
@@ -795,13 +869,6 @@ function clone(object) {
|
|||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove flag
|
|
||||||
function removeFlag(str) {
|
|
||||||
return str
|
|
||||||
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, '')
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDynamicFunction(name, script, $arguments) {
|
function createDynamicFunction(name, script, $arguments) {
|
||||||
const flowUtils = {
|
const flowUtils = {
|
||||||
getFlowField,
|
getFlowField,
|
||||||
@@ -809,6 +876,7 @@ function createDynamicFunction(name, script, $arguments) {
|
|||||||
parseFlowHeaders,
|
parseFlowHeaders,
|
||||||
flowTransfer,
|
flowTransfer,
|
||||||
validCheck,
|
validCheck,
|
||||||
|
getRmainingDays,
|
||||||
};
|
};
|
||||||
if ($.env.isLoon) {
|
if ($.env.isLoon) {
|
||||||
return new Function(
|
return new Function(
|
||||||
|
|||||||
@@ -144,12 +144,26 @@ export default function Clash_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -160,11 +160,25 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import singbox_Producer from './sing-box';
|
|||||||
|
|
||||||
function JSON_Producer() {
|
function JSON_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => JSON.stringify(proxies, null, 2);
|
const produce = (proxies, type) =>
|
||||||
|
type === 'internal' ? proxies : JSON.stringify(proxies, null, 2);
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export default function Loon_Producer() {
|
|||||||
return vless(proxy);
|
return vless(proxy);
|
||||||
case 'http':
|
case 'http':
|
||||||
return http(proxy);
|
return http(proxy);
|
||||||
|
case 'socks5':
|
||||||
|
return socks5(proxy);
|
||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
return wireguard(proxy);
|
return wireguard(proxy);
|
||||||
case 'hysteria2':
|
case 'hysteria2':
|
||||||
@@ -83,7 +85,9 @@ function shadowsocks(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -109,7 +113,9 @@ function shadowsocksr(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -152,7 +158,9 @@ function trojan(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
@@ -219,7 +227,9 @@ function vmess(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +291,9 @@ function vless(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,8 +316,29 @@ function http(proxy) {
|
|||||||
// tfo
|
// tfo
|
||||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
return result.toString();
|
||||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
}
|
||||||
|
function socks5(proxy) {
|
||||||
|
const result = new Result(proxy);
|
||||||
|
result.append(`${proxy.name}=socks5,${proxy.server},${proxy.port}`);
|
||||||
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
|
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||||
|
|
||||||
|
// tls
|
||||||
|
result.appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
||||||
|
|
||||||
|
// sni
|
||||||
|
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||||
|
|
||||||
|
// tls verification
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||||
|
'skip-cert-verify',
|
||||||
|
);
|
||||||
|
|
||||||
|
// tfo
|
||||||
|
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +369,11 @@ function wireguard(proxy) {
|
|||||||
if (proxy.dns) {
|
if (proxy.dns) {
|
||||||
if (Array.isArray(proxy.dns)) {
|
if (Array.isArray(proxy.dns)) {
|
||||||
proxy.dnsv6 = proxy.dns.find((i) => isIPv6(i));
|
proxy.dnsv6 = proxy.dns.find((i) => isIPv6(i));
|
||||||
proxy.dns = proxy.dns.find((i) => isIPv4(i));
|
let dns = proxy.dns.find((i) => isIPv4(i));
|
||||||
|
if (!dns) {
|
||||||
|
dns = proxy.dns.find((i) => !isIPv4(i) && !isIPv6(i));
|
||||||
|
}
|
||||||
|
proxy.dns = dns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.appendIfPresent(`,dns=${proxy.dns}`, 'dns');
|
result.appendIfPresent(`,dns=${proxy.dns}`, 'dns');
|
||||||
@@ -390,7 +427,9 @@ function hysteria2(proxy) {
|
|||||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||||
|
|
||||||
// udp
|
// udp
|
||||||
result.appendIfPresent(`,udp=${proxy.udp}`, 'udp');
|
if (proxy.udp) {
|
||||||
|
result.append(`,udp=true`);
|
||||||
|
}
|
||||||
|
|
||||||
// download-bandwidth
|
// download-bandwidth
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { isPresent, Result } from './utils';
|
|||||||
const targetPlatform = 'QX';
|
const targetPlatform = 'QX';
|
||||||
|
|
||||||
export default function QX_Producer() {
|
export default function QX_Producer() {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const produce = (proxy, type, opts = {}) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
@@ -18,13 +19,7 @@ export default function QX_Producer() {
|
|||||||
case 'socks5':
|
case 'socks5':
|
||||||
return socks5(proxy);
|
return socks5(proxy);
|
||||||
case 'vless':
|
case 'vless':
|
||||||
if (opts['include-unsupported-proxy']) {
|
return vless(proxy);
|
||||||
return vless(proxy);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Platform ${targetPlatform}(App Store Release) does not support proxy type: ${proxy.type}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||||
|
|||||||
@@ -163,11 +163,25 @@ export default function ShadowRocket_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import ClashMeta_Producer from './clashmeta';
|
import ClashMeta_Producer from './clashmeta';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
|
||||||
|
const detourParser = (proxy, parsedProxy) => {
|
||||||
|
if (proxy['dialer-proxy']) parsedProxy.detour = proxy['dialer-proxy'];
|
||||||
|
};
|
||||||
const tfoParser = (proxy, parsedProxy) => {
|
const tfoParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.tcp_fast_open = false;
|
parsedProxy.tcp_fast_open = false;
|
||||||
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
||||||
@@ -178,8 +182,8 @@ const grpcParser = (proxy, parsedProxy) => {
|
|||||||
const transport = { type: 'grpc' };
|
const transport = { type: 'grpc' };
|
||||||
if (proxy['grpc-opts']) {
|
if (proxy['grpc-opts']) {
|
||||||
const serviceName = proxy['grpc-opts']['grpc-service-name'];
|
const serviceName = proxy['grpc-opts']['grpc-service-name'];
|
||||||
if (serviceName && serviceName !== '')
|
if (serviceName != null && serviceName !== '')
|
||||||
transport.service_name = serviceName;
|
transport.service_name = `${serviceName}`;
|
||||||
}
|
}
|
||||||
parsedProxy.transport = transport;
|
parsedProxy.transport = transport;
|
||||||
};
|
};
|
||||||
@@ -217,6 +221,42 @@ const tlsParser = (proxy, parsedProxy) => {
|
|||||||
if (!parsedProxy.tls.enabled) delete parsedProxy.tls;
|
if (!parsedProxy.tls.enabled) delete parsedProxy.tls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sshParser = (proxy = {}) => {
|
||||||
|
const parsedProxy = {
|
||||||
|
tag: proxy.name,
|
||||||
|
type: 'ssh',
|
||||||
|
server: proxy.server,
|
||||||
|
server_port: parseInt(`${proxy.port}`, 10),
|
||||||
|
};
|
||||||
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
|
throw 'invalid port';
|
||||||
|
if (proxy.username) parsedProxy.user = proxy.username;
|
||||||
|
if (proxy.password) parsedProxy.password = proxy.password;
|
||||||
|
// https://wiki.metacubex.one/config/proxies/ssh
|
||||||
|
// https://sing-box.sagernet.org/zh/configuration/outbound/ssh
|
||||||
|
if (proxy['privateKey']) parsedProxy.private_key_path = proxy['privateKey'];
|
||||||
|
if (proxy['private-key'])
|
||||||
|
parsedProxy.private_key_path = proxy['private-key'];
|
||||||
|
if (proxy['private-key-passphrase'])
|
||||||
|
parsedProxy.private_key_passphrase = proxy['private-key-passphrase'];
|
||||||
|
if (proxy['server-fingerprint']) {
|
||||||
|
parsedProxy.host_key = [proxy['server-fingerprint']];
|
||||||
|
// https://manual.nssurge.com/policy/ssh.html
|
||||||
|
// Surge only supports curve25519-sha256 as the kex algorithm and aes128-gcm as the encryption algorithm. It means that the SSH server must use OpenSSH v7.3 or above. (It should not be a problem since OpenSSH 7.3 was released on 2016-08-01.)
|
||||||
|
// TODO: ?
|
||||||
|
parsedProxy.host_key_algorithms = [
|
||||||
|
proxy['server-fingerprint'].split(' ')[0],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (proxy['host-key']) parsedProxy.host_key = proxy['host-key'];
|
||||||
|
if (proxy['host-key-algorithms'])
|
||||||
|
parsedProxy.host_key_algorithms = proxy['host-key-algorithms'];
|
||||||
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
|
return parsedProxy;
|
||||||
|
};
|
||||||
|
|
||||||
const httpParser = (proxy = {}) => {
|
const httpParser = (proxy = {}) => {
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
@@ -225,7 +265,7 @@ const httpParser = (proxy = {}) => {
|
|||||||
server_port: parseInt(`${proxy.port}`, 10),
|
server_port: parseInt(`${proxy.port}`, 10),
|
||||||
tls: { enabled: false, server_name: proxy.server, insecure: false },
|
tls: { enabled: false, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.username) parsedProxy.username = proxy.username;
|
if (proxy.username) parsedProxy.username = proxy.username;
|
||||||
if (proxy.password) parsedProxy.password = proxy.password;
|
if (proxy.password) parsedProxy.password = proxy.password;
|
||||||
@@ -239,6 +279,7 @@ const httpParser = (proxy = {}) => {
|
|||||||
}
|
}
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -252,7 +293,7 @@ const socks5Parser = (proxy = {}) => {
|
|||||||
password: proxy.password,
|
password: proxy.password,
|
||||||
version: '5',
|
version: '5',
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.username) parsedProxy.username = proxy.username;
|
if (proxy.username) parsedProxy.username = proxy.username;
|
||||||
if (proxy.password) parsedProxy.password = proxy.password;
|
if (proxy.password) parsedProxy.password = proxy.password;
|
||||||
@@ -260,6 +301,7 @@ const socks5Parser = (proxy = {}) => {
|
|||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,10 +329,11 @@ const shadowTLSParser = (proxy = {}) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (stPart.server_port < 1 || stPart.server_port > 65535)
|
if (stPart.server_port < 0 || stPart.server_port > 65535)
|
||||||
throw '端口值非法';
|
throw '端口值非法';
|
||||||
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
|
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
|
||||||
tfoParser(proxy, stPart);
|
tfoParser(proxy, stPart);
|
||||||
|
detourParser(proxy, stPart);
|
||||||
smuxParser(proxy.smux, ssPart);
|
smuxParser(proxy.smux, ssPart);
|
||||||
return { type: 'ss-with-st', ssPart, stPart };
|
return { type: 'ss-with-st', ssPart, stPart };
|
||||||
};
|
};
|
||||||
@@ -303,12 +346,13 @@ const ssParser = (proxy = {}) => {
|
|||||||
method: proxy.cipher,
|
method: proxy.cipher,
|
||||||
password: proxy.password,
|
password: proxy.password,
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
if (proxy.uot) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
if (proxy.plugin) {
|
if (proxy.plugin) {
|
||||||
const optArr = [];
|
const optArr = [];
|
||||||
@@ -379,13 +423,14 @@ const ssrParser = (proxy = {}) => {
|
|||||||
obfs: proxy.obfs,
|
obfs: proxy.obfs,
|
||||||
protocol: proxy.protocol,
|
protocol: proxy.protocol,
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['obfs-param']) parsedProxy.obfs_param = proxy['obfs-param'];
|
if (proxy['obfs-param']) parsedProxy.obfs_param = proxy['obfs-param'];
|
||||||
if (proxy['protocol-param'] && proxy['protocol-param'] !== '')
|
if (proxy['protocol-param'] && proxy['protocol-param'] !== '')
|
||||||
parsedProxy.protocol_param = proxy['protocol-param'];
|
parsedProxy.protocol_param = proxy['protocol-param'];
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -412,7 +457,7 @@ const vmessParser = (proxy = {}) => {
|
|||||||
].indexOf(parsedProxy.security) === -1
|
].indexOf(parsedProxy.security) === -1
|
||||||
)
|
)
|
||||||
parsedProxy.security = 'auto';
|
parsedProxy.security = 'auto';
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
|
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
@@ -422,6 +467,7 @@ const vmessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -436,7 +482,7 @@ const vlessParser = (proxy = {}) => {
|
|||||||
uuid: proxy.uuid,
|
uuid: proxy.uuid,
|
||||||
tls: { enabled: false, server_name: proxy.server, insecure: false },
|
tls: { enabled: false, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
if (proxy.flow === 'xtls-rprx-vision') parsedProxy.flow = proxy.flow;
|
||||||
@@ -444,6 +490,7 @@ const vlessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -457,13 +504,14 @@ const trojanParser = (proxy = {}) => {
|
|||||||
password: proxy.password,
|
password: proxy.password,
|
||||||
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -477,7 +525,7 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
disable_mtu_discovery: false,
|
disable_mtu_discovery: false,
|
||||||
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.auth_str) parsedProxy.auth_str = `${proxy.auth_str}`;
|
if (proxy.auth_str) parsedProxy.auth_str = `${proxy.auth_str}`;
|
||||||
if (proxy['auth-str']) parsedProxy.auth_str = `${proxy['auth-str']}`;
|
if (proxy['auth-str']) parsedProxy.auth_str = `${proxy['auth-str']}`;
|
||||||
@@ -510,6 +558,7 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -524,7 +573,7 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
obfs: {},
|
obfs: {},
|
||||||
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
if (proxy.up) parsedProxy.up_mbps = parseInt(`${proxy.up}`, 10);
|
||||||
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
if (proxy.down) parsedProxy.down_mbps = parseInt(`${proxy.down}`, 10);
|
||||||
@@ -534,6 +583,7 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -547,7 +597,7 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
password: proxy.password,
|
password: proxy.password,
|
||||||
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (
|
if (
|
||||||
@@ -562,6 +612,7 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
if (proxy['heartbeat-interval'])
|
if (proxy['heartbeat-interval'])
|
||||||
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -570,8 +621,11 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
const wireguardParser = (proxy = {}) => {
|
const wireguardParser = (proxy = {}) => {
|
||||||
const local_address = ['ip', 'ipv6']
|
const local_address = ['ip', 'ipv6']
|
||||||
.map((i) => proxy[i])
|
.map((i) => proxy[i])
|
||||||
.filter((i) => i)
|
.map((i) => {
|
||||||
.map((i) => (/\\/.test(i) ? i : `${i}/32`));
|
if (isIPv4(i)) return `${i}/32`;
|
||||||
|
if (isIPv6(i)) return `${i}/128`;
|
||||||
|
})
|
||||||
|
.filter((i) => i);
|
||||||
const parsedProxy = {
|
const parsedProxy = {
|
||||||
tag: proxy.name,
|
tag: proxy.name,
|
||||||
type: 'wireguard',
|
type: 'wireguard',
|
||||||
@@ -583,13 +637,15 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
pre_shared_key: proxy['pre-shared-key'],
|
pre_shared_key: proxy['pre-shared-key'],
|
||||||
reserved: [],
|
reserved: [],
|
||||||
};
|
};
|
||||||
if (parsedProxy.server_port < 1 || parsedProxy.server_port > 65535)
|
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
|
||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (typeof proxy.reserved === 'string') {
|
if (typeof proxy.reserved === 'string') {
|
||||||
parsedProxy.reserved.push(proxy.reserved);
|
parsedProxy.reserved = proxy.reserved;
|
||||||
} else {
|
} else if (Array.isArray(proxy.reserved)) {
|
||||||
for (const r of proxy.reserved) parsedProxy.reserved.push(r);
|
for (const r of proxy.reserved) parsedProxy.reserved.push(r);
|
||||||
|
} else {
|
||||||
|
delete parsedProxy.reserved;
|
||||||
}
|
}
|
||||||
if (proxy.peers && proxy.peers.length > 0) {
|
if (proxy.peers && proxy.peers.length > 0) {
|
||||||
parsedProxy.peers = [];
|
parsedProxy.peers = [];
|
||||||
@@ -603,14 +659,17 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
};
|
};
|
||||||
if (typeof p.reserved === 'string') {
|
if (typeof p.reserved === 'string') {
|
||||||
peer.reserved.push(p.reserved);
|
peer.reserved.push(p.reserved);
|
||||||
} else {
|
} else if (Array.isArray(p.reserved)) {
|
||||||
for (const r of p.reserved) peer.reserved.push(r);
|
for (const r of p.reserved) peer.reserved.push(r);
|
||||||
|
} else {
|
||||||
|
delete peer.reserved;
|
||||||
}
|
}
|
||||||
if (p['pre-shared-key']) peer.pre_shared_key = p['pre-shared-key'];
|
if (p['pre-shared-key']) peer.pre_shared_key = p['pre-shared-key'];
|
||||||
parsedProxy.peers.push(peer);
|
parsedProxy.peers.push(peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -624,6 +683,9 @@ export default function singbox_Producer() {
|
|||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
try {
|
try {
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
|
case 'ssh':
|
||||||
|
list.push(sshParser(proxy));
|
||||||
|
break;
|
||||||
case 'http':
|
case 'http':
|
||||||
list.push(httpParser(proxy));
|
list.push(httpParser(proxy));
|
||||||
break;
|
break;
|
||||||
@@ -747,7 +809,10 @@ export default function singbox_Producer() {
|
|||||||
$.error(e.message ?? e);
|
$.error(e.message ?? e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return type === 'internal' ? list : JSON.stringify(list, null, 2);
|
|
||||||
|
return type === 'internal'
|
||||||
|
? list
|
||||||
|
: JSON.stringify({ outbounds: list }, null, 2);
|
||||||
};
|
};
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,9 +239,15 @@ export default function Stash_Producer() {
|
|||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy['server-cert-fingerprint'] = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -250,9 +256,21 @@ export default function Stash_Producer() {
|
|||||||
proxy['benchmark-url'] = proxy['test-url'];
|
proxy['benchmark-url'] = proxy['test-url'];
|
||||||
delete proxy['test-url'];
|
delete proxy['test-url'];
|
||||||
}
|
}
|
||||||
|
if (proxy['test-timeout']) {
|
||||||
|
proxy['benchmark-timeout'] = proxy['test-timeout'];
|
||||||
|
delete proxy['test-timeout'];
|
||||||
|
}
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const targetPlatform = 'Surfboard';
|
|||||||
|
|
||||||
export default function Surfboard_Producer() {
|
export default function Surfboard_Producer() {
|
||||||
const produce = (proxy) => {
|
const produce = (proxy) => {
|
||||||
proxy.name = proxy.name.replace(/=/g, '');
|
proxy.name = proxy.name.replace(/=|,/g, '');
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Result, isPresent } from './utils';
|
import { Result, isPresent } from './utils';
|
||||||
import { isNotBlank } from '@/utils';
|
import { isNotBlank, getIfNotBlank } from '@/utils';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
const targetPlatform = 'Surge';
|
const targetPlatform = 'Surge';
|
||||||
@@ -13,14 +13,15 @@ const ipVersions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Surge_Producer() {
|
export default function Surge_Producer() {
|
||||||
const produce = (proxy) => {
|
const produce = (proxy, type, opts = {}) => {
|
||||||
|
proxy.name = proxy.name.replace(/=|,/g, '');
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return shadowsocks(proxy);
|
return shadowsocks(proxy);
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
return trojan(proxy);
|
return trojan(proxy);
|
||||||
case 'vmess':
|
case 'vmess':
|
||||||
return vmess(proxy);
|
return vmess(proxy, opts['include-unsupported-proxy']);
|
||||||
case 'http':
|
case 'http':
|
||||||
return http(proxy);
|
return http(proxy);
|
||||||
case 'socks5':
|
case 'socks5':
|
||||||
@@ -30,9 +31,15 @@ export default function Surge_Producer() {
|
|||||||
case 'tuic':
|
case 'tuic':
|
||||||
return tuic(proxy);
|
return tuic(proxy);
|
||||||
case 'wireguard-surge':
|
case 'wireguard-surge':
|
||||||
return wireguard(proxy);
|
return wireguard_surge(proxy);
|
||||||
case 'hysteria2':
|
case 'hysteria2':
|
||||||
return hysteria2(proxy);
|
return hysteria2(proxy);
|
||||||
|
case 'ssh':
|
||||||
|
return ssh(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts['include-unsupported-proxy'] && proxy.type === 'wireguard') {
|
||||||
|
return wireguard(proxy);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||||
@@ -82,10 +89,8 @@ function shadowsocks(proxy) {
|
|||||||
result.append(`,encrypt-method=${proxy.cipher}`);
|
result.append(`,encrypt-method=${proxy.cipher}`);
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -117,6 +122,21 @@ function shadowsocks(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -167,10 +187,8 @@ function trojan(proxy) {
|
|||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -204,6 +222,21 @@ function trojan(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -231,15 +264,13 @@ function trojan(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmess(proxy) {
|
function vmess(proxy, includeUnsupportedProxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
|
||||||
result.appendIfPresent(`,username=${proxy.uuid}`, 'uuid');
|
result.appendIfPresent(`,username=${proxy.uuid}`, 'uuid');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -247,7 +278,7 @@ function vmess(proxy) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// transport
|
// transport
|
||||||
handleTransport(result, proxy);
|
handleTransport(result, proxy, includeUnsupportedProxy);
|
||||||
|
|
||||||
// AEAD
|
// AEAD
|
||||||
if (isPresent(proxy, 'aead')) {
|
if (isPresent(proxy, 'aead')) {
|
||||||
@@ -280,6 +311,21 @@ function vmess(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -307,6 +353,71 @@ function vmess(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ssh(proxy) {
|
||||||
|
const result = new Result(proxy);
|
||||||
|
result.append(`${proxy.name}=ssh,${proxy.server},${proxy.port}`);
|
||||||
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
|
// 所有的类似的字段都有双引号的问题 暂不处理
|
||||||
|
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||||
|
|
||||||
|
// https://manual.nssurge.com/policy/ssh.html
|
||||||
|
// 需配合 Keystore
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,private-key=${proxy['keystore-private-key']}`,
|
||||||
|
'keystore-private-key',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,idle-timeout=${proxy['idle-timeout']}`,
|
||||||
|
'idle-timeout',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,server-fingerprint="${proxy['server-fingerprint']}"`,
|
||||||
|
'server-fingerprint',
|
||||||
|
);
|
||||||
|
|
||||||
|
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 http(proxy) {
|
function http(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
const type = proxy.tls ? 'https' : 'http';
|
const type = proxy.tls ? 'https' : 'http';
|
||||||
@@ -314,10 +425,8 @@ function http(proxy) {
|
|||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -345,6 +454,21 @@ function http(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -379,10 +503,8 @@ function socks5(proxy) {
|
|||||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||||
result.appendIfPresent(`,${proxy.password}`, 'password');
|
result.appendIfPresent(`,${proxy.password}`, 'password');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -412,6 +534,21 @@ function socks5(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -445,10 +582,8 @@ function snell(proxy) {
|
|||||||
result.appendIfPresent(`,version=${proxy.version}`, 'version');
|
result.appendIfPresent(`,version=${proxy.version}`, 'version');
|
||||||
result.appendIfPresent(`,psk=${proxy.psk}`, 'psk');
|
result.appendIfPresent(`,psk=${proxy.psk}`, 'psk');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -477,6 +612,21 @@ function snell(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -525,10 +675,8 @@ function tuic(proxy) {
|
|||||||
'alpn',
|
'alpn',
|
||||||
);
|
);
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -557,6 +705,21 @@ function tuic(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -587,6 +750,122 @@ function tuic(proxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function wireguard(proxy) {
|
function wireguard(proxy) {
|
||||||
|
if (Array.isArray(proxy.peers) && proxy.peers.length > 0) {
|
||||||
|
proxy.server = proxy.peers[0].server;
|
||||||
|
proxy.port = proxy.peers[0].port;
|
||||||
|
proxy.ip = proxy.peers[0].ip;
|
||||||
|
proxy.ipv6 = proxy.peers[0].ipv6;
|
||||||
|
proxy['public-key'] = proxy.peers[0]['public-key'];
|
||||||
|
proxy['preshared-key'] = proxy.peers[0]['pre-shared-key'];
|
||||||
|
// https://github.com/MetaCubeX/mihomo/blob/0404e35be8736b695eae018a08debb175c1f96e6/docs/config.yaml#L717
|
||||||
|
proxy['allowed-ips'] = proxy.peers[0]['allowed-ips'];
|
||||||
|
proxy.reserved = proxy.peers[0].reserved;
|
||||||
|
}
|
||||||
|
const result = new Result(proxy);
|
||||||
|
|
||||||
|
result.append(`# > WireGuard Proxy ${proxy.name}
|
||||||
|
# ${proxy.name}=wireguard`);
|
||||||
|
|
||||||
|
proxy['section-name'] = getIfNotBlank(proxy['section-name'], proxy.name);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,section-name=${proxy['section-name']}`,
|
||||||
|
'section-name',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
|
'no-error-alert',
|
||||||
|
);
|
||||||
|
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
);
|
||||||
|
|
||||||
|
// shadow-tls
|
||||||
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
|
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
|
||||||
|
'shadow-tls-version',
|
||||||
|
);
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
|
||||||
|
'shadow-tls-sni',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// block-quic
|
||||||
|
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
|
||||||
|
|
||||||
|
// underlying-proxy
|
||||||
|
result.appendIfPresent(
|
||||||
|
`,underlying-proxy=${proxy['underlying-proxy']}`,
|
||||||
|
'underlying-proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
result.append(`
|
||||||
|
# > WireGuard Section ${proxy.name}
|
||||||
|
[WireGuard ${proxy['section-name']}]
|
||||||
|
private-key = ${proxy['private-key']}`);
|
||||||
|
|
||||||
|
result.appendIfPresent(`\nself-ip = ${proxy.ip}`, 'ip');
|
||||||
|
result.appendIfPresent(`\nself-ip-v6 = ${proxy.ipv6}`, 'ipv6');
|
||||||
|
if (proxy.dns) {
|
||||||
|
if (Array.isArray(proxy.dns)) {
|
||||||
|
proxy.dns = proxy.dns.join(', ');
|
||||||
|
}
|
||||||
|
result.append(`\ndns-server = ${proxy.dns}`);
|
||||||
|
}
|
||||||
|
result.appendIfPresent(`\nmtu = ${proxy.mtu}`, 'mtu');
|
||||||
|
|
||||||
|
if (ip_version === 'prefer-v6') {
|
||||||
|
result.append(`\nprefer-ipv6 = true`);
|
||||||
|
}
|
||||||
|
const allowedIps = Array.isArray(proxy['allowed-ips'])
|
||||||
|
? proxy['allowed-ips'].join(',')
|
||||||
|
: proxy['allowed-ips'];
|
||||||
|
let reserved = Array.isArray(proxy.reserved)
|
||||||
|
? proxy.reserved.join('/')
|
||||||
|
: proxy.reserved;
|
||||||
|
let presharedKey = proxy['preshared-key'] ?? proxy['pre-shared-key'];
|
||||||
|
if (presharedKey) {
|
||||||
|
presharedKey = `,preshared-key="${presharedKey}"`;
|
||||||
|
}
|
||||||
|
const peer = {
|
||||||
|
'public-key': proxy['public-key'],
|
||||||
|
'allowed-ips': allowedIps ? `"${allowedIps}"` : undefined,
|
||||||
|
endpoint: `${proxy.server}:${proxy.port}`,
|
||||||
|
keepalive: proxy['persistent-keepalive'] || proxy.keepalive,
|
||||||
|
'client-id': reserved,
|
||||||
|
'preshared-key': presharedKey,
|
||||||
|
};
|
||||||
|
result.append(
|
||||||
|
`\npeer = (${Object.keys(peer)
|
||||||
|
.filter((k) => peer[k] != null)
|
||||||
|
.map((k) => `${k} = ${peer[k]}`)
|
||||||
|
.join(', ')})`,
|
||||||
|
);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
function wireguard_surge(proxy) {
|
||||||
const result = new Result(proxy);
|
const result = new Result(proxy);
|
||||||
|
|
||||||
result.append(`${proxy.name}=wireguard`);
|
result.append(`${proxy.name}=wireguard`);
|
||||||
@@ -600,13 +879,26 @@ function wireguard(proxy) {
|
|||||||
'no-error-alert',
|
'no-error-alert',
|
||||||
);
|
);
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -643,10 +935,8 @@ function hysteria2(proxy) {
|
|||||||
|
|
||||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||||
|
|
||||||
result.appendIfPresent(
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
`,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`,
|
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
|
||||||
'ip-version',
|
|
||||||
);
|
|
||||||
|
|
||||||
result.appendIfPresent(
|
result.appendIfPresent(
|
||||||
`,no-error-alert=${proxy['no-error-alert']}`,
|
`,no-error-alert=${proxy['no-error-alert']}`,
|
||||||
@@ -673,6 +963,21 @@ function hysteria2(proxy) {
|
|||||||
|
|
||||||
// test-url
|
// test-url
|
||||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, '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',
|
||||||
|
);
|
||||||
|
|
||||||
// shadow-tls
|
// shadow-tls
|
||||||
if (isPresent(proxy, 'shadow-tls-password')) {
|
if (isPresent(proxy, 'shadow-tls-password')) {
|
||||||
@@ -708,7 +1013,7 @@ function hysteria2(proxy) {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTransport(result, proxy) {
|
function handleTransport(result, proxy, includeUnsupportedProxy) {
|
||||||
if (isPresent(proxy, 'network')) {
|
if (isPresent(proxy, 'network')) {
|
||||||
if (proxy.network === 'ws') {
|
if (proxy.network === 'ws') {
|
||||||
result.append(`,ws=true`);
|
result.append(`,ws=true`);
|
||||||
@@ -734,7 +1039,13 @@ function handleTransport(result, proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`network ${proxy.network} is unsupported`);
|
if (includeUnsupportedProxy && ['http'].includes(proxy.network)) {
|
||||||
|
$.info(
|
||||||
|
`Include Unsupported Proxy: nework ${proxy.network} -> tcp`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`network ${proxy.network} is unsupported`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ export default function URI_Producer() {
|
|||||||
let result = '';
|
let result = '';
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
delete proxy['no-resolve'];
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -19,7 +27,7 @@ export default function URI_Producer() {
|
|||||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||||
result = `ss://${Base64.encode(userinfo)}@${proxy.server}:${
|
result = `ss://${Base64.encode(userinfo)}@${proxy.server}:${
|
||||||
proxy.port
|
proxy.port
|
||||||
}/`;
|
}${proxy.plugin ? '/' : ''}`;
|
||||||
if (proxy.plugin) {
|
if (proxy.plugin) {
|
||||||
result += '?plugin=';
|
result += '?plugin=';
|
||||||
const opts = proxy['plugin-opts'];
|
const opts = proxy['plugin-opts'];
|
||||||
@@ -48,7 +56,9 @@ export default function URI_Producer() {
|
|||||||
result = `${result}${proxy.plugin ? '&' : '?'}uot=1`;
|
result = `${result}${proxy.plugin ? '&' : '?'}uot=1`;
|
||||||
}
|
}
|
||||||
if (proxy.tfo) {
|
if (proxy.tfo) {
|
||||||
result = `${result}${proxy.plugin ? '&' : '?'}tfo=1`;
|
result = `${result}${
|
||||||
|
proxy.plugin || proxy['udp-over-tcp'] ? '&' : '?'
|
||||||
|
}tfo=1`;
|
||||||
}
|
}
|
||||||
result += `#${encodeURIComponent(proxy.name)}`;
|
result += `#${encodeURIComponent(proxy.name)}`;
|
||||||
break;
|
break;
|
||||||
@@ -75,6 +85,11 @@ export default function URI_Producer() {
|
|||||||
if (proxy.network === 'http') {
|
if (proxy.network === 'http') {
|
||||||
net = 'tcp';
|
net = 'tcp';
|
||||||
type = 'http';
|
type = 'http';
|
||||||
|
} else if (
|
||||||
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
net = 'httpupgrade';
|
||||||
}
|
}
|
||||||
result = {
|
result = {
|
||||||
v: '2',
|
v: '2',
|
||||||
@@ -163,9 +178,15 @@ export default function URI_Producer() {
|
|||||||
if (proxy.flow) {
|
if (proxy.flow) {
|
||||||
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
flow = `&flow=${encodeURIComponent(proxy.flow)}`;
|
||||||
}
|
}
|
||||||
let vlessTransport = `&type=${encodeURIComponent(
|
let vlessType = proxy.network;
|
||||||
proxy.network,
|
if (
|
||||||
)}`;
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
vlessType = 'httpupgrade';
|
||||||
|
}
|
||||||
|
|
||||||
|
let vlessTransport = `&type=${encodeURIComponent(vlessType)}`;
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
// https://github.com/XTLS/Xray-core/issues/91
|
// https://github.com/XTLS/Xray-core/issues/91
|
||||||
vlessTransport += `&mode=${encodeURIComponent(
|
vlessTransport += `&mode=${encodeURIComponent(
|
||||||
@@ -202,16 +223,23 @@ export default function URI_Producer() {
|
|||||||
|
|
||||||
result = `vless://${proxy.uuid}@${proxy.server}:${
|
result = `vless://${proxy.uuid}@${proxy.server}:${
|
||||||
proxy.port
|
proxy.port
|
||||||
}?${vlessTransport}&security=${encodeURIComponent(
|
}?security=${encodeURIComponent(
|
||||||
security,
|
security,
|
||||||
)}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
)}${vlessTransport}${alpn}${allowInsecure}${sni}${fp}${flow}${sid}${pbk}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
break;
|
||||||
case 'trojan':
|
case 'trojan':
|
||||||
let trojanTransport = '';
|
let trojanTransport = '';
|
||||||
if (proxy.network) {
|
if (proxy.network) {
|
||||||
trojanTransport = `&type=${proxy.network}`;
|
let trojanType = proxy.network;
|
||||||
|
if (
|
||||||
|
proxy.network === 'ws' &&
|
||||||
|
proxy['ws-opts']?.['v2ray-http-upgrade']
|
||||||
|
) {
|
||||||
|
trojanType = 'httpupgrade';
|
||||||
|
}
|
||||||
|
trojanTransport = `&type=${encodeURIComponent(trojanType)}`;
|
||||||
if (['grpc'].includes(proxy.network)) {
|
if (['grpc'].includes(proxy.network)) {
|
||||||
let trojanTransportServiceName =
|
let trojanTransportServiceName =
|
||||||
proxy[`${proxy.network}-opts`]?.[
|
proxy[`${proxy.network}-opts`]?.[
|
||||||
@@ -274,6 +302,9 @@ export default function URI_Producer() {
|
|||||||
`sni=${encodeURIComponent(proxy.sni)}`,
|
`sni=${encodeURIComponent(proxy.sni)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (proxy.ports) {
|
||||||
|
hysteria2params.push(`mport=${proxy.ports}`);
|
||||||
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
hysteria2params.push(
|
hysteria2params.push(
|
||||||
`pinSHA256=${encodeURIComponent(
|
`pinSHA256=${encodeURIComponent(
|
||||||
@@ -401,8 +432,51 @@ export default function URI_Producer() {
|
|||||||
}?${tuicParams.join('&')}#${encodeURIComponent(
|
}?${tuicParams.join('&')}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case 'wireguard':
|
||||||
|
let wireguardParams = [];
|
||||||
|
|
||||||
|
Object.keys(proxy).forEach((key) => {
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'server',
|
||||||
|
'port',
|
||||||
|
'ip',
|
||||||
|
'ipv6',
|
||||||
|
'private-key',
|
||||||
|
].includes(key)
|
||||||
|
) {
|
||||||
|
if (['public-key'].includes(key)) {
|
||||||
|
wireguardParams.push(`publickey=${proxy[key]}`);
|
||||||
|
} else if (['udp'].includes(key)) {
|
||||||
|
if (proxy[key]) {
|
||||||
|
wireguardParams.push(`${key}=1`);
|
||||||
|
}
|
||||||
|
} else if (proxy[key]) {
|
||||||
|
wireguardParams.push(
|
||||||
|
`${key}=${encodeURIComponent(proxy[key])}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (proxy.ip && proxy.ipv6) {
|
||||||
|
wireguardParams.push(
|
||||||
|
`address=${proxy.ip}/32,${proxy.ipv6}/128`,
|
||||||
|
);
|
||||||
|
} else if (proxy.ip) {
|
||||||
|
wireguardParams.push(`address=${proxy.ip}/32`);
|
||||||
|
} else if (proxy.ipv6) {
|
||||||
|
wireguardParams.push(`address=${proxy.ipv6}/128`);
|
||||||
|
}
|
||||||
|
result = `wireguard://${encodeURIComponent(
|
||||||
|
proxy['private-key'],
|
||||||
|
)}@${proxy.server}:${proxy.port}/?${wireguardParams.join(
|
||||||
|
'&',
|
||||||
|
)}#${encodeURIComponent(proxy.name)}`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ const RULE_TYPES_MAPPING = [
|
|||||||
[/^(IN|SRC)-PORT$/, 'IN-PORT'],
|
[/^(IN|SRC)-PORT$/, 'IN-PORT'],
|
||||||
[/^PROTOCOL$/, 'PROTOCOL'],
|
[/^PROTOCOL$/, 'PROTOCOL'],
|
||||||
[/^IP-CIDR$/i, 'IP-CIDR'],
|
[/^IP-CIDR$/i, 'IP-CIDR'],
|
||||||
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/],
|
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/, 'IP-CIDR6'],
|
||||||
|
[/^GEOIP$/i, 'GEOIP'],
|
||||||
|
[/^GEOSITE$/i, 'GEOSITE'],
|
||||||
];
|
];
|
||||||
|
|
||||||
function AllRuleParser() {
|
function AllRuleParser() {
|
||||||
@@ -37,8 +39,7 @@ function AllRuleParser() {
|
|||||||
content: params[1],
|
content: params[1],
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
rule.type === 'IP-CIDR' ||
|
['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)
|
||||||
rule.type === 'IP-CIDR6'
|
|
||||||
) {
|
) {
|
||||||
rule.options = params.slice(2);
|
rule.options = params.slice(2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ function QXFilter() {
|
|||||||
'SRC-IP',
|
'SRC-IP',
|
||||||
'IN-PORT',
|
'IN-PORT',
|
||||||
'PROTOCOL',
|
'PROTOCOL',
|
||||||
|
'GEOSITE',
|
||||||
|
'GEOIP',
|
||||||
];
|
];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
|
|
||||||
@@ -29,9 +31,12 @@ function QXFilter() {
|
|||||||
function SurgeRuleSet() {
|
function SurgeRuleSet() {
|
||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
|
const UNSUPPORTED = ['GEOSITE', 'GEOIP'];
|
||||||
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
let output = `${rule.type},${rule.content}`;
|
let output = `${rule.type},${rule.content}`;
|
||||||
if (rule.type === 'IP-CIDR' || rule.type === 'IP-CIDR6') {
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
||||||
output += rule.options ? `,${rule.options[0]}` : '';
|
output +=
|
||||||
|
rule.options?.length > 0 ? `,${rule.options.join(',')}` : '';
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
@@ -42,8 +47,14 @@ function LoonRules() {
|
|||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
// skip unsupported rules
|
// skip unsupported rules
|
||||||
const UNSUPPORTED = ['DEST-PORT', 'SRC-IP', 'IN-PORT', 'PROTOCOL'];
|
const UNSUPPORTED = ['SRC-IP', 'GEOSITE', 'GEOIP'];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type) && rule.options) {
|
||||||
|
// Loon only supports the no-resolve option
|
||||||
|
rule.options = rule.options.filter((option) =>
|
||||||
|
['no-resolve'].includes(option),
|
||||||
|
);
|
||||||
|
}
|
||||||
return SurgeRuleSet().func(rule);
|
return SurgeRuleSet().func(rule);
|
||||||
};
|
};
|
||||||
return { type, func };
|
return { type, func };
|
||||||
@@ -62,8 +73,17 @@ function ClashRuleProvider() {
|
|||||||
let output = `${TRANSFORM[rule.type] || rule.type},${
|
let output = `${TRANSFORM[rule.type] || rule.type},${
|
||||||
rule.content
|
rule.content
|
||||||
}`;
|
}`;
|
||||||
if (rule.type === 'IP-CIDR' || rule.type === 'IP-CIDR6') {
|
if (['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)) {
|
||||||
output += rule.options ? `,${rule.options[0]}` : '';
|
if (rule.options) {
|
||||||
|
// Clash only supports the no-resolve option
|
||||||
|
rule.options = rule.options.filter((option) =>
|
||||||
|
['no-resolve'].includes(option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
output +=
|
||||||
|
rule.options?.length > 0
|
||||||
|
? `,${rule.options.join(',')}`
|
||||||
|
: '';
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,21 +1,75 @@
|
|||||||
import { version } from '../../package.json';
|
import { version } from '../../package.json';
|
||||||
import { SETTINGS_KEY, ARTIFACTS_KEY } from '@/constants';
|
import {
|
||||||
|
SETTINGS_KEY,
|
||||||
|
ARTIFACTS_KEY,
|
||||||
|
SUBS_KEY,
|
||||||
|
COLLECTIONS_KEY,
|
||||||
|
} from '@/constants';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { syncToGist } from '@/restful/artifacts';
|
import { syncToGist } from '@/restful/artifacts';
|
||||||
|
import { findByName } from '@/utils/database';
|
||||||
|
|
||||||
!(async function () {
|
!(async function () {
|
||||||
const settings = $.read(SETTINGS_KEY);
|
let arg;
|
||||||
// if GitHub token is not configured
|
if (typeof $argument != 'undefined') {
|
||||||
if (!settings.githubUser || !settings.gistToken) return;
|
arg = Object.fromEntries(
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
$argument.split('&').map((item) => item.split('=')),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
arg = {};
|
||||||
|
}
|
||||||
|
let sub_names = (arg?.subscription ?? arg?.sub ?? '')
|
||||||
|
.split(/,|,/g)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length > 0)
|
||||||
|
.map((i) => decodeURIComponent(i));
|
||||||
|
let col_names = (arg?.collection ?? arg?.col ?? '')
|
||||||
|
.split(/,|,/g)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length > 0)
|
||||||
|
.map((i) => decodeURIComponent(i));
|
||||||
|
if (sub_names.length > 0 || col_names.length > 0) {
|
||||||
|
if (sub_names.length > 0)
|
||||||
|
await produceArtifacts(sub_names, 'subscription');
|
||||||
|
if (col_names.length > 0)
|
||||||
|
await produceArtifacts(col_names, 'collection');
|
||||||
|
} else {
|
||||||
|
const settings = $.read(SETTINGS_KEY);
|
||||||
|
// if GitHub token is not configured
|
||||||
|
if (!settings.githubUser || !settings.gistToken) return;
|
||||||
|
|
||||||
const artifacts = $.read(ARTIFACTS_KEY);
|
const artifacts = $.read(ARTIFACTS_KEY);
|
||||||
if (!artifacts || artifacts.length === 0) return;
|
if (!artifacts || artifacts.length === 0) return;
|
||||||
|
|
||||||
const shouldSync = artifacts.some((artifact) => artifact.sync);
|
const shouldSync = artifacts.some((artifact) => artifact.sync);
|
||||||
if (shouldSync) await doSync();
|
if (shouldSync) await doSync();
|
||||||
|
}
|
||||||
})().finally(() => $.done());
|
})().finally(() => $.done());
|
||||||
|
|
||||||
|
async function produceArtifacts(names, type) {
|
||||||
|
try {
|
||||||
|
if (names.length > 0) {
|
||||||
|
$.info(`produceArtifacts ${type} 开始: ${names.join(', ')}`);
|
||||||
|
await Promise.all(
|
||||||
|
names.map(async (name) => {
|
||||||
|
try {
|
||||||
|
await produceArtifact({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`${type} ${name} error: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
$.info(`produceArtifacts ${type} 完成: ${names.join(', ')}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`produceArtifacts error: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function doSync() {
|
async function doSync() {
|
||||||
console.log(
|
console.log(
|
||||||
`
|
`
|
||||||
@@ -30,23 +84,84 @@ async function doSync() {
|
|||||||
const files = {};
|
const files = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const invalid = [];
|
||||||
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
|
const subNames = [];
|
||||||
|
allArtifacts.map((artifact) => {
|
||||||
|
if (artifact.sync && artifact.source) {
|
||||||
|
if (artifact.type === 'subscription') {
|
||||||
|
const subName = artifact.source;
|
||||||
|
const sub = findByName(allSubs, subName);
|
||||||
|
if (sub && sub.url && !subNames.includes(subName)) {
|
||||||
|
subNames.push(subName);
|
||||||
|
}
|
||||||
|
} else if (artifact.type === 'collection') {
|
||||||
|
const collection = findByName(allCols, artifact.source);
|
||||||
|
if (collection && Array.isArray(collection.subscriptions)) {
|
||||||
|
collection.subscriptions.map((subName) => {
|
||||||
|
const sub = findByName(allSubs, subName);
|
||||||
|
if (sub && sub.url && !subNames.includes(subName)) {
|
||||||
|
subNames.push(subName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subNames.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
subNames.map(async (subName) => {
|
||||||
|
try {
|
||||||
|
await produceArtifact({
|
||||||
|
type: 'subscription',
|
||||||
|
name: subName,
|
||||||
|
awaitCustomCache: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// $.error(`${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allArtifacts.map(async (artifact) => {
|
allArtifacts.map(async (artifact) => {
|
||||||
if (artifact.sync) {
|
try {
|
||||||
$.info(`正在同步云配置:${artifact.name}...`);
|
if (artifact.sync && artifact.source) {
|
||||||
const output = await produceArtifact({
|
$.info(`正在同步云配置:${artifact.name}...`);
|
||||||
type: artifact.type,
|
const output = await produceArtifact({
|
||||||
name: artifact.source,
|
type: artifact.type,
|
||||||
platform: artifact.platform,
|
name: artifact.source,
|
||||||
});
|
platform: artifact.platform,
|
||||||
|
produceOpts: {
|
||||||
|
'include-unsupported-proxy':
|
||||||
|
artifact.includeUnsupportedProxy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
files[encodeURIComponent(artifact.name)] = {
|
// if (!output || output.length === 0)
|
||||||
content: output,
|
// throw new Error('该配置的结果为空 不进行上传');
|
||||||
};
|
|
||||||
|
files[encodeURIComponent(artifact.name)] = {
|
||||||
|
content: output,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
invalid.push(artifact.name);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (invalid.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const resp = await syncToGist(files);
|
const resp = await syncToGist(files);
|
||||||
const body = JSON.parse(resp.body);
|
const body = JSON.parse(resp.body);
|
||||||
|
|
||||||
@@ -62,17 +177,26 @@ async function doSync() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${err}`);
|
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${e.message ?? e}`);
|
||||||
$.error(`无法同步订阅配置到 Gist,原因:${err}`);
|
$.error(`无法同步订阅配置到 Gist,原因:${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,79 @@
|
|||||||
import { ProxyUtils } from '@/core/proxy-utils';
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { RuleUtils } from '@/core/rule-utils';
|
import { RuleUtils } from '@/core/rule-utils';
|
||||||
import { version } from '../../package.json';
|
import { version } from '../../package.json';
|
||||||
|
import download from '@/utils/download';
|
||||||
|
|
||||||
console.log(
|
let result = '';
|
||||||
`
|
let resource = typeof $resource !== 'undefined' ? $resource : '';
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
let resourceType = typeof $resourceType !== 'undefined' ? $resourceType : '';
|
||||||
Sub-Store -- v${version}
|
let resourceUrl = typeof $resourceUrl !== 'undefined' ? $resourceUrl : '';
|
||||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const RESOURCE_TYPE = {
|
!(async () => {
|
||||||
PROXY: 1,
|
console.log(
|
||||||
RULE: 2,
|
`
|
||||||
};
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
|
Sub-Store -- v${version}
|
||||||
|
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
let result = $resource;
|
let arg;
|
||||||
|
if (typeof $argument != 'undefined') {
|
||||||
|
arg = Object.fromEntries(
|
||||||
|
$argument.split('&').map((item) => item.split('=')),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
arg = {};
|
||||||
|
}
|
||||||
|
|
||||||
if ($resourceType === RESOURCE_TYPE.PROXY) {
|
const RESOURCE_TYPE = {
|
||||||
const proxies = ProxyUtils.parse($resource);
|
PROXY: 1,
|
||||||
result = ProxyUtils.produce(proxies, 'Loon');
|
RULE: 2,
|
||||||
} else if ($resourceType === RESOURCE_TYPE.RULE) {
|
};
|
||||||
const rules = RuleUtils.parse($resource);
|
|
||||||
result = RuleUtils.produce(rules, 'Loon');
|
|
||||||
}
|
|
||||||
|
|
||||||
$done(result);
|
result = resource;
|
||||||
|
|
||||||
|
if (resourceType === RESOURCE_TYPE.PROXY) {
|
||||||
|
try {
|
||||||
|
let proxies = ProxyUtils.parse(resource);
|
||||||
|
result = ProxyUtils.produce(proxies, 'Loon');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('解析器: 使用 resource 出现错误');
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取订阅`);
|
||||||
|
try {
|
||||||
|
let raw = await download(resourceUrl, arg?.ua, arg?.timeout);
|
||||||
|
let proxies = ProxyUtils.parse(raw);
|
||||||
|
result = ProxyUtils.produce(proxies, 'Loon');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (resourceType === RESOURCE_TYPE.RULE) {
|
||||||
|
try {
|
||||||
|
const rules = RuleUtils.parse(resource);
|
||||||
|
result = RuleUtils.produce(rules, 'Loon');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
|
if ((!result || /^\s*$/.test(result)) && resourceUrl) {
|
||||||
|
console.log(`解析器: 尝试从 ${resourceUrl} 获取规则`);
|
||||||
|
try {
|
||||||
|
let raw = await download(resourceUrl, arg?.ua, arg?.timeout);
|
||||||
|
let rules = RuleUtils.parse(raw);
|
||||||
|
result = RuleUtils.produce(rules, 'Loon');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.catch(async (e) => {
|
||||||
|
console.log('解析器: 出现错误');
|
||||||
|
console.log(e.message ?? e);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
$done(result || '');
|
||||||
|
});
|
||||||
|
|||||||
@@ -253,7 +253,25 @@ async function syncToGist(files) {
|
|||||||
key: ARTIFACT_REPOSITORY_KEY,
|
key: ARTIFACT_REPOSITORY_KEY,
|
||||||
syncPlatform,
|
syncPlatform,
|
||||||
});
|
});
|
||||||
return manager.upload(files);
|
const res = await manager.upload(files);
|
||||||
|
let body = {};
|
||||||
|
try {
|
||||||
|
body = JSON.parse(res.body);
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const url = body?.html_url ?? body?.web_url;
|
||||||
|
const settings = $.read(SETTINGS_KEY);
|
||||||
|
if (url) {
|
||||||
|
$.log(`同步 Gist 后, 找到 Sub-Store Gist: ${url}`);
|
||||||
|
settings.artifactStore = url;
|
||||||
|
settings.artifactStoreStatus = 'VALID';
|
||||||
|
} else {
|
||||||
|
$.error(`同步 Gist 后, 找不到 Sub-Store Gist`);
|
||||||
|
settings.artifactStoreStatus = 'NOT FOUND';
|
||||||
|
}
|
||||||
|
$.write(settings, SETTINGS_KEY);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { syncToGist };
|
export { syncToGist };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getPlatformFromHeaders } from '@/utils/platform';
|
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
||||||
|
import { ProxyUtils } from '@/core/proxy-utils';
|
||||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { getFlowHeaders } from '@/utils/flow';
|
import { getFlowHeaders } from '@/utils/flow';
|
||||||
@@ -6,20 +7,59 @@ import $ from '@/core/app';
|
|||||||
import { failed } from '@/restful/response';
|
import { failed } from '@/restful/response';
|
||||||
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
import { getISO } from '@/utils/geo';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
$app.get('/download/collection/:name', downloadCollection);
|
$app.get('/download/collection/:name', downloadCollection);
|
||||||
$app.get('/download/:name', downloadSubscription);
|
$app.get('/download/:name', downloadSubscription);
|
||||||
|
$app.get(
|
||||||
|
'/download/collection/:name/api/v1/server/details',
|
||||||
|
async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha';
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
$app.get('/download/:name/api/v1/server/details', async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha';
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
|
$app.get(
|
||||||
|
'/download/collection/:name/api/v1/monitor/:nezhaIndex',
|
||||||
|
async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha-monitor';
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
$app.get('/download/:name/api/v1/monitor/:nezhaIndex', async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha-monitor';
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadSubscription(req, res) {
|
async function downloadSubscription(req, res) {
|
||||||
let { name } = req.params;
|
let { name, nezhaIndex } = req.params;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
|
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
|
|
||||||
$.info(`正在下载订阅:${name}`);
|
$.info(
|
||||||
|
`正在下载订阅:${name}\n请求 User-Agent: ${
|
||||||
|
req.headers['user-agent'] || req.headers['User-Agent']
|
||||||
|
}`,
|
||||||
|
);
|
||||||
let {
|
let {
|
||||||
url,
|
url,
|
||||||
ua,
|
ua,
|
||||||
@@ -28,6 +68,7 @@ async function downloadSubscription(req, res) {
|
|||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
produceType,
|
produceType,
|
||||||
includeUnsupportedProxy,
|
includeUnsupportedProxy,
|
||||||
|
resultFormat,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
@@ -62,7 +103,7 @@ async function downloadSubscription(req, res) {
|
|||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
try {
|
try {
|
||||||
const output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name,
|
name,
|
||||||
platform,
|
platform,
|
||||||
@@ -77,12 +118,49 @@ async function downloadSubscription(req, res) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sub.source !== 'local' || url) {
|
if (
|
||||||
|
sub.source !== 'local' ||
|
||||||
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// forward flow headers
|
url = `${url || sub.url}`
|
||||||
const flowInfo = await getFlowHeaders(url || sub.url);
|
.split(/[\r\n]+/)
|
||||||
if (flowInfo) {
|
.map((i) => i.trim())
|
||||||
res.set('subscription-userinfo', flowInfo);
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(
|
||||||
|
decodeURIComponent(rawArgs[1]),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$arguments.noFlow) {
|
||||||
|
// forward flow headers
|
||||||
|
const flowInfo = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
|
);
|
||||||
|
if (flowInfo) {
|
||||||
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
@@ -92,8 +170,23 @@ async function downloadSubscription(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
|
res.set('subscription-userinfo', sub.subUserinfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
|
if (resultFormat === 'nezha') {
|
||||||
|
output = nezhaTransform(output);
|
||||||
|
} else if (resultFormat === 'nezha-monitor') {
|
||||||
|
nezhaIndex = /^\d+$/.test(nezhaIndex)
|
||||||
|
? parseInt(nezhaIndex, 10)
|
||||||
|
: output.findIndex((i) => i.name === nezhaIndex);
|
||||||
|
output = await nezhaMonitor(
|
||||||
|
output[nezhaIndex],
|
||||||
|
nezhaIndex,
|
||||||
|
req.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -130,8 +223,9 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function downloadCollection(req, res) {
|
async function downloadCollection(req, res) {
|
||||||
let { name } = req.params;
|
let { name, nezhaIndex } = req.params;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
|
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||||
|
|
||||||
const platform =
|
const platform =
|
||||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||||
@@ -139,10 +233,18 @@ async function downloadCollection(req, res) {
|
|||||||
const allCols = $.read(COLLECTIONS_KEY);
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
const collection = findByName(allCols, name);
|
const collection = findByName(allCols, name);
|
||||||
|
|
||||||
$.info(`正在下载组合订阅:${name}`);
|
$.info(
|
||||||
|
`正在下载组合订阅:${name}\n请求 User-Agent: ${
|
||||||
|
req.headers['user-agent'] || req.headers['User-Agent']
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
|
||||||
let { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy } =
|
let {
|
||||||
req.query;
|
ignoreFailedRemoteSub,
|
||||||
|
produceType,
|
||||||
|
includeUnsupportedProxy,
|
||||||
|
resultFormat,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
||||||
@@ -160,7 +262,7 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
try {
|
try {
|
||||||
const output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
name,
|
name,
|
||||||
platform,
|
platform,
|
||||||
@@ -176,11 +278,48 @@ async function downloadCollection(req, res) {
|
|||||||
const subnames = collection.subscriptions;
|
const subnames = collection.subscriptions;
|
||||||
if (subnames.length > 0) {
|
if (subnames.length > 0) {
|
||||||
const sub = findByName(allSubs, subnames[0]);
|
const sub = findByName(allSubs, subnames[0]);
|
||||||
if (sub.source !== 'local') {
|
if (
|
||||||
|
sub.source !== 'local' ||
|
||||||
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const flowInfo = await getFlowHeaders(sub.url);
|
let url = `${sub.url}`
|
||||||
if (flowInfo) {
|
.split(/[\r\n]+/)
|
||||||
res.set('subscription-userinfo', flowInfo);
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(
|
||||||
|
decodeURIComponent(rawArgs[1]),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$arguments.noFlow) {
|
||||||
|
const flowInfo = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
|
);
|
||||||
|
if (flowInfo) {
|
||||||
|
res.set('subscription-userinfo', flowInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
@@ -190,9 +329,24 @@ async function downloadCollection(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
|
res.set('subscription-userinfo', sub.subUserinfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
|
if (resultFormat === 'nezha') {
|
||||||
|
output = nezhaTransform(output);
|
||||||
|
} else if (resultFormat === 'nezha-monitor') {
|
||||||
|
nezhaIndex = /^\d+$/.test(nezhaIndex)
|
||||||
|
? parseInt(nezhaIndex, 10)
|
||||||
|
: output.findIndex((i) => i.name === nezhaIndex);
|
||||||
|
output = await nezhaMonitor(
|
||||||
|
output[nezhaIndex],
|
||||||
|
nezhaIndex,
|
||||||
|
req.query,
|
||||||
|
);
|
||||||
|
}
|
||||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -229,3 +383,149 @@ async function downloadCollection(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function nezhaMonitor(proxy, index, query) {
|
||||||
|
const result = {
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
result: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { isLoon, isSurge } = $.env;
|
||||||
|
if (!isLoon && !isSurge)
|
||||||
|
throw new Error('仅支持 Loon 和 Surge(ability=http-client-policy)');
|
||||||
|
const node = ProxyUtils.produce([proxy], isLoon ? 'Loon' : 'Surge');
|
||||||
|
if (!node) throw new Error('当前客户端不兼容此节点');
|
||||||
|
const monitors = proxy._monitors || [
|
||||||
|
{
|
||||||
|
name: 'Cloudflare',
|
||||||
|
url: 'http://cp.cloudflare.com/generate_204',
|
||||||
|
method: 'HEAD',
|
||||||
|
number: 3,
|
||||||
|
timeout: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google',
|
||||||
|
url: 'http://www.google.com/generate_204',
|
||||||
|
method: 'HEAD',
|
||||||
|
number: 3,
|
||||||
|
timeout: 2000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const number =
|
||||||
|
query.number || Math.max(...monitors.map((i) => i.number)) || 3;
|
||||||
|
for (const monitor of monitors) {
|
||||||
|
const interval = 10 * 60 * 1000;
|
||||||
|
const data = {
|
||||||
|
monitor_id: monitors.indexOf(monitor),
|
||||||
|
server_id: index,
|
||||||
|
monitor_name: monitor.name,
|
||||||
|
server_name: proxy.name,
|
||||||
|
created_at: [],
|
||||||
|
avg_delay: [],
|
||||||
|
};
|
||||||
|
for (let index = 0; index < number; index++) {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
try {
|
||||||
|
await $.http[(monitor.method || 'HEAD').toLowerCase()]({
|
||||||
|
timeout: monitor.timeout || 2000,
|
||||||
|
url: monitor.url,
|
||||||
|
'policy-descriptor': node,
|
||||||
|
node,
|
||||||
|
});
|
||||||
|
const latency = Date.now() - startedAt;
|
||||||
|
$.info(`${monitor.name} latency: ${latency}`);
|
||||||
|
data.avg_delay.push(latency);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(e);
|
||||||
|
data.avg_delay.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.created_at.push(
|
||||||
|
Date.now() - interval * (monitor.number - index - 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.result.push(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(e);
|
||||||
|
result.result.push({
|
||||||
|
monitor_id: 0,
|
||||||
|
server_id: 0,
|
||||||
|
monitor_name: `❌ ${e.message ?? e}`,
|
||||||
|
server_name: proxy.name,
|
||||||
|
created_at: [Date.now()],
|
||||||
|
avg_delay: [0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(result, null, 2);
|
||||||
|
}
|
||||||
|
function nezhaTransform(output) {
|
||||||
|
const result = {
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
result: [],
|
||||||
|
};
|
||||||
|
output.map((proxy, index) => {
|
||||||
|
// 如果节点上有数据 就取节点上的数据
|
||||||
|
let CountryCode = proxy._geo?.countryCode || proxy._geo?.country;
|
||||||
|
// 简单判断下
|
||||||
|
if (!/^[a-z]{2}$/i.test(CountryCode)) {
|
||||||
|
CountryCode = getISO(proxy.name);
|
||||||
|
}
|
||||||
|
// 简单判断下
|
||||||
|
if (/^[a-z]{2}$/i.test(CountryCode)) {
|
||||||
|
// 如果节点上有数据 就取节点上的数据
|
||||||
|
let now = Math.round(new Date().getTime() / 1000);
|
||||||
|
let time = proxy._unavailable ? 0 : now;
|
||||||
|
|
||||||
|
const uptime = parseInt(proxy._uptime || 0, 10);
|
||||||
|
|
||||||
|
result.result.push({
|
||||||
|
id: index,
|
||||||
|
name: proxy.name,
|
||||||
|
tag: `${proxy._tag ?? ''}`,
|
||||||
|
last_active: time,
|
||||||
|
// 暂时不用处理 现在 VPings App 端的接口支持域名查询
|
||||||
|
// 其他场景使用 自己在 Sub-Store 加一步域名解析
|
||||||
|
valid_ip: proxy._IP || proxy.server,
|
||||||
|
ipv4: proxy._IPv4 || proxy.server,
|
||||||
|
ipv6: proxy._IPv6 || (isIPv6(proxy.server) ? proxy.server : ''),
|
||||||
|
host: {
|
||||||
|
Platform: 'Sub-Store',
|
||||||
|
PlatformVersion: env.version,
|
||||||
|
CPU: [],
|
||||||
|
MemTotal: 1024,
|
||||||
|
DiskTotal: 1024,
|
||||||
|
SwapTotal: 1024,
|
||||||
|
Arch: '',
|
||||||
|
Virtualization: '',
|
||||||
|
BootTime: now - uptime,
|
||||||
|
CountryCode, // 目前需要
|
||||||
|
Version: '0.0.1',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
CPU: 0,
|
||||||
|
MemUsed: 0,
|
||||||
|
SwapUsed: 0,
|
||||||
|
DiskUsed: 0,
|
||||||
|
NetInTransfer: 0,
|
||||||
|
NetOutTransfer: 0,
|
||||||
|
NetInSpeed: 0,
|
||||||
|
NetOutSpeed: 0,
|
||||||
|
Uptime: uptime,
|
||||||
|
Load1: 0,
|
||||||
|
Load5: 0,
|
||||||
|
Load15: 0,
|
||||||
|
TcpConnCount: 0,
|
||||||
|
UdpConnCount: 0,
|
||||||
|
ProcessCount: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(result, null, 2);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ENV } from '@/vendor/open-api';
|
|||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
import {
|
import {
|
||||||
GIST_BACKUP_FILE_NAME,
|
GIST_BACKUP_FILE_NAME,
|
||||||
GIST_BACKUP_KEY,
|
GIST_BACKUP_KEY,
|
||||||
@@ -73,6 +75,8 @@ async function refresh(_, res) {
|
|||||||
|
|
||||||
// 2. clear resource cache
|
// 2. clear resource cache
|
||||||
resourceCache.revokeAll();
|
resourceCache.revokeAll();
|
||||||
|
scriptResourceCache.revokeAll();
|
||||||
|
headersResourceCache.revokeAll();
|
||||||
success(res);
|
success(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +157,14 @@ async function gistBackup(req, res) {
|
|||||||
}
|
}
|
||||||
success(res);
|
success(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`Failed to ${action} gist data.\nReason: ${err.message ?? err}`,
|
||||||
|
);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
'BACKUP_FAILED',
|
'BACKUP_FAILED',
|
||||||
`Failed to ${action} data to gist!`,
|
`Failed to ${action} gist data!`,
|
||||||
`Reason: ${err.message ?? err}`,
|
`Reason: ${err.message ?? err}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -109,7 +109,12 @@ async function compareSub(req, res) {
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -195,7 +200,12 @@ async function compareCollection(req, res) {
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -243,11 +253,7 @@ async function compareCollection(req, res) {
|
|||||||
errors[name] = err;
|
errors[name] = err;
|
||||||
|
|
||||||
$.error(
|
$.error(
|
||||||
`❌ 处理组合订阅中的子订阅: ${
|
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}!`,
|
||||||
sub.name
|
|
||||||
}时出现错误:${err}!进度--${
|
|
||||||
100 * (processed / subnames.length).toFixed(1)
|
|
||||||
}%`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import {
|
|||||||
} from './errors';
|
} from './errors';
|
||||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||||
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||||
import { getFlowHeaders, parseFlowHeaders } from '@/utils/flow';
|
import {
|
||||||
|
getFlowHeaders,
|
||||||
|
parseFlowHeaders,
|
||||||
|
getRmainingDays,
|
||||||
|
} from '@/utils/flow';
|
||||||
import { success, failed } from './response';
|
import { success, failed } from './response';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
@@ -43,32 +47,99 @@ async function getFlowInfo(req, res) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sub.source === 'local') {
|
if (
|
||||||
failed(
|
sub.source === 'local' &&
|
||||||
res,
|
!['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
new RequestInvalidError(
|
) {
|
||||||
'NO_FLOW_INFO',
|
if (sub.subUserinfo) {
|
||||||
'N/A',
|
success(res, {
|
||||||
`Local subscription ${name} has no flow information!`,
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
),
|
});
|
||||||
);
|
} else {
|
||||||
|
failed(
|
||||||
|
res,
|
||||||
|
new RequestInvalidError(
|
||||||
|
'NO_FLOW_INFO',
|
||||||
|
'N/A',
|
||||||
|
`Local subscription ${name} has no flow information!`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const flowHeaders = await getFlowHeaders(sub.url);
|
let url = `${sub.url}`
|
||||||
if (!flowHeaders) {
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)?.[0];
|
||||||
|
|
||||||
|
let $arguments = {};
|
||||||
|
const rawArgs = url.split('#');
|
||||||
|
url = url.split('#')[0];
|
||||||
|
if (rawArgs.length > 1) {
|
||||||
|
try {
|
||||||
|
// 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}`
|
||||||
|
$arguments = JSON.parse(decodeURIComponent(rawArgs[1]));
|
||||||
|
} catch (e) {
|
||||||
|
for (const pair of rawArgs[1].split('&')) {
|
||||||
|
const key = pair.split('=')[0];
|
||||||
|
const value = pair.split('=')[1];
|
||||||
|
// 部分兼容之前的逻辑 const value = pair.split('=')[1] || true;
|
||||||
|
$arguments[key] =
|
||||||
|
value == null || value === ''
|
||||||
|
? true
|
||||||
|
: decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($arguments.noFlow) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new RequestInvalidError(
|
||||||
'NO_FLOW_INFO',
|
'NO_FLOW_INFO',
|
||||||
'No flow info',
|
'N/A',
|
||||||
`Failed to fetch flow headers`,
|
`Subscription ${name}: noFlow`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (sub.subUserinfo) {
|
||||||
success(res, parseFlowHeaders(flowHeaders));
|
success(res, {
|
||||||
|
...parseFlowHeaders(sub.subUserinfo),
|
||||||
|
remainingDays: getRmainingDays({
|
||||||
|
resetDay: $arguments.resetDay,
|
||||||
|
startDate: $arguments.startDate,
|
||||||
|
cycleDays: $arguments.cycleDays,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const flowHeaders = await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
@@ -111,11 +182,21 @@ function createSubscription(req, res) {
|
|||||||
|
|
||||||
function getSubscription(req, res) {
|
function getSubscription(req, res) {
|
||||||
let { name } = req.params;
|
let { name } = req.params;
|
||||||
|
let { raw } = req.query;
|
||||||
name = decodeURIComponent(name);
|
name = decodeURIComponent(name);
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
success(res, sub);
|
if (raw) {
|
||||||
|
res.set('content-type', 'application/json')
|
||||||
|
.set(
|
||||||
|
'content-disposition',
|
||||||
|
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
||||||
|
)
|
||||||
|
.send(JSON.stringify(sub));
|
||||||
|
} else {
|
||||||
|
success(res, sub);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
|
|||||||
@@ -35,13 +35,22 @@ async function produceArtifact({
|
|||||||
ignoreFailedRemoteFile,
|
ignoreFailedRemoteFile,
|
||||||
produceType,
|
produceType,
|
||||||
produceOpts = {},
|
produceOpts = {},
|
||||||
|
subscription,
|
||||||
|
awaitCustomCache,
|
||||||
}) {
|
}) {
|
||||||
platform = platform || 'JSON';
|
platform = platform || 'JSON';
|
||||||
|
|
||||||
if (type === 'subscription') {
|
if (type === 'subscription') {
|
||||||
const allSubs = $.read(SUBS_KEY);
|
let sub;
|
||||||
const sub = findByName(allSubs, name);
|
if (name) {
|
||||||
if (!sub) throw new Error(`找不到订阅 ${name}`);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
sub = findByName(allSubs, name);
|
||||||
|
if (!sub) throw new Error(`找不到订阅 ${name}`);
|
||||||
|
} else if (subscription) {
|
||||||
|
sub = subscription;
|
||||||
|
} else {
|
||||||
|
throw new Error('未提供订阅名称或订阅数据');
|
||||||
|
}
|
||||||
let raw;
|
let raw;
|
||||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
||||||
raw = content;
|
raw = content;
|
||||||
@@ -54,7 +63,14 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
undefined,
|
||||||
|
awaitCustomCache,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -94,7 +110,14 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, ua || sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
undefined,
|
||||||
|
awaitCustomCache,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -190,7 +213,12 @@ async function produceArtifact({
|
|||||||
.filter((i) => i.length)
|
.filter((i) => i.length)
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
try {
|
try {
|
||||||
return await download(url, sub.ua);
|
return await download(
|
||||||
|
url,
|
||||||
|
sub.ua,
|
||||||
|
undefined,
|
||||||
|
sub.proxy,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors[url] = err;
|
errors[url] = err;
|
||||||
$.error(
|
$.error(
|
||||||
@@ -448,6 +476,47 @@ async function syncArtifacts() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const invalid = [];
|
const invalid = [];
|
||||||
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
const allCols = $.read(COLLECTIONS_KEY);
|
||||||
|
const subNames = [];
|
||||||
|
allArtifacts.map((artifact) => {
|
||||||
|
if (artifact.sync && artifact.source) {
|
||||||
|
if (artifact.type === 'subscription') {
|
||||||
|
const subName = artifact.source;
|
||||||
|
const sub = findByName(allSubs, subName);
|
||||||
|
if (sub && sub.url && !subNames.includes(subName)) {
|
||||||
|
subNames.push(subName);
|
||||||
|
}
|
||||||
|
} else if (artifact.type === 'collection') {
|
||||||
|
const collection = findByName(allCols, artifact.source);
|
||||||
|
if (collection && Array.isArray(collection.subscriptions)) {
|
||||||
|
collection.subscriptions.map((subName) => {
|
||||||
|
const sub = findByName(allSubs, subName);
|
||||||
|
if (sub && sub.url && !subNames.includes(subName)) {
|
||||||
|
subNames.push(subName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subNames.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
subNames.map(async (subName) => {
|
||||||
|
try {
|
||||||
|
await produceArtifact({
|
||||||
|
type: 'subscription',
|
||||||
|
name: subName,
|
||||||
|
awaitCustomCache: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// $.error(`${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allArtifacts.map(async (artifact) => {
|
allArtifacts.map(async (artifact) => {
|
||||||
try {
|
try {
|
||||||
@@ -500,10 +569,19 @@ async function syncArtifacts() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,10 +675,18 @@ async function syncArtifact(req, res) {
|
|||||||
isGitLab = true;
|
isGitLab = true;
|
||||||
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
artifact.url = isGitLab
|
const new_url = isGitLab
|
||||||
? url
|
? raw_url
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, artifact);
|
success(res, artifact);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FILES_KEY, MODULES_KEY, SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
|
||||||
import { HTTP, ENV } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
import { hex_md5 } from '@/vendor/md5';
|
import { hex_md5 } from '@/vendor/md5';
|
||||||
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +14,14 @@ import $ from '@/core/app';
|
|||||||
|
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
|
|
||||||
export default async function download(rawUrl, ua, timeout) {
|
export default async function download(
|
||||||
|
rawUrl,
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
skipCustomCache,
|
||||||
|
awaitCustomCache,
|
||||||
|
) {
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
let url = rawUrl.replace(/#noFlow$/, '');
|
let url = rawUrl.replace(/#noFlow$/, '');
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
@@ -35,29 +42,85 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const { isNode, isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
const { defaultUserAgent, defaultTimeout, cacheThreshold } =
|
||||||
if (downloadUrlMatch) {
|
$.read(SETTINGS_KEY);
|
||||||
let type = downloadUrlMatch?.[1];
|
|
||||||
let name = downloadUrlMatch?.[2];
|
|
||||||
if (name == null) {
|
|
||||||
throw new Error(`本地 ${type} URL 无效: ${url}`);
|
|
||||||
}
|
|
||||||
name = decodeURIComponent(name);
|
|
||||||
const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
|
||||||
const item = findByName($.read(key), name);
|
|
||||||
if (!item) {
|
|
||||||
throw new Error(`找不到本地 ${type}: ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isNode } = ENV();
|
|
||||||
const { defaultUserAgent, defaultTimeout } = $.read(SETTINGS_KEY);
|
|
||||||
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
const userAgent = ua || defaultUserAgent || 'clash.meta';
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const id = hex_md5(userAgent + url);
|
const id = hex_md5(userAgent + url);
|
||||||
|
|
||||||
|
const customCacheKey = $arguments?.cacheKey
|
||||||
|
? `#sub-store-cached-custom-${$arguments?.cacheKey}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (customCacheKey && !skipCustomCache) {
|
||||||
|
const customCached = $.read(customCacheKey);
|
||||||
|
const cached = resourceCache.get(id);
|
||||||
|
if (!$arguments?.noCache && cached) {
|
||||||
|
$.info(
|
||||||
|
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
||||||
|
);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
if (customCached) {
|
||||||
|
if (awaitCustomCache) {
|
||||||
|
$.info(`乐观缓存: URL ${url}\n本次进行请求 尝试更新缓存`);
|
||||||
|
try {
|
||||||
|
await download(
|
||||||
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`乐观缓存: URL ${url} 更新缓存发生错误 ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
$.info('使用乐观缓存的数据刷新缓存, 防止后续请求');
|
||||||
|
resourceCache.set(id, customCached);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$.info(
|
||||||
|
`乐观缓存: URL ${url}\n本次返回自定义缓存 ${$arguments?.cacheKey}\n并进行请求 尝试异步更新缓存`,
|
||||||
|
);
|
||||||
|
download(
|
||||||
|
rawUrl.replace(/(\?|&)cacheKey=.*?(&|$)/, ''),
|
||||||
|
ua,
|
||||||
|
timeout,
|
||||||
|
proxy,
|
||||||
|
true,
|
||||||
|
).catch((e) => {
|
||||||
|
$.error(
|
||||||
|
`乐观缓存: URL ${url} 异步更新缓存发生错误 ${
|
||||||
|
e.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return customCached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
|
// if (downloadUrlMatch) {
|
||||||
|
// let type = downloadUrlMatch?.[1];
|
||||||
|
// let name = downloadUrlMatch?.[2];
|
||||||
|
// if (name == null) {
|
||||||
|
// throw new Error(`本地 ${type} URL 无效: ${url}`);
|
||||||
|
// }
|
||||||
|
// name = decodeURIComponent(name);
|
||||||
|
// const key = type === 'module' ? MODULES_KEY : FILES_KEY;
|
||||||
|
// const item = findByName($.read(key), name);
|
||||||
|
// if (!item) {
|
||||||
|
// throw new Error(`找不到本地 ${type}: ${name}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return item.content;
|
||||||
|
// }
|
||||||
|
|
||||||
if (!isNode && tasks.has(id)) {
|
if (!isNode && tasks.has(id)) {
|
||||||
return tasks.get(id);
|
return tasks.get(id);
|
||||||
}
|
}
|
||||||
@@ -65,6 +128,10 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
const http = HTTP({
|
const http = HTTP({
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? { 'X-Stash-Selected-Proxy': encodeURIComponent(proxy) }
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy ? { 'X-Surge-Policy': proxy } : {}),
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
});
|
});
|
||||||
@@ -74,13 +141,24 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
// try to find in app cache
|
// try to find in app cache
|
||||||
const cached = resourceCache.get(id);
|
const cached = resourceCache.get(id);
|
||||||
if (!$arguments?.noCache && cached) {
|
if (!$arguments?.noCache && cached) {
|
||||||
|
$.info(`使用缓存: ${url}`);
|
||||||
result = cached;
|
result = cached;
|
||||||
|
if (customCacheKey) {
|
||||||
|
$.info(`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`);
|
||||||
|
$.write(cached, customCacheKey);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$.info(
|
$.info(
|
||||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nURL: ${url}`,
|
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const { body, headers } = await http.get(url);
|
const { body, headers } = await http.get({
|
||||||
|
url,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
});
|
||||||
|
|
||||||
if (headers) {
|
if (headers) {
|
||||||
const flowInfo = getFlowField(headers);
|
const flowInfo = getFlowField(headers);
|
||||||
@@ -90,10 +168,41 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
}
|
}
|
||||||
if (body.replace(/\s/g, '').length === 0)
|
if (body.replace(/\s/g, '').length === 0)
|
||||||
throw new Error(new Error('远程资源内容为空'));
|
throw new Error(new Error('远程资源内容为空'));
|
||||||
|
let shouldCache = true;
|
||||||
|
if (cacheThreshold) {
|
||||||
|
const size = body.length / 1024;
|
||||||
|
if (size > cacheThreshold) {
|
||||||
|
$.info(
|
||||||
|
`资源大小 ${size.toFixed(
|
||||||
|
2,
|
||||||
|
)} KB 超过了 ${cacheThreshold} KB, 不缓存`,
|
||||||
|
);
|
||||||
|
shouldCache = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldCache) {
|
||||||
|
resourceCache.set(id, body);
|
||||||
|
if (customCacheKey) {
|
||||||
|
$.info(
|
||||||
|
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
|
||||||
|
);
|
||||||
|
$.write(body, customCacheKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resourceCache.set(id, body);
|
|
||||||
result = body;
|
result = body;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (customCacheKey) {
|
||||||
|
const cached = $.read(customCacheKey);
|
||||||
|
if (cached) {
|
||||||
|
$.info(
|
||||||
|
`无法下载 URL ${url}: ${
|
||||||
|
e.message ?? e
|
||||||
|
}\n使用自定义缓存 ${$arguments?.cacheKey}`,
|
||||||
|
);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
|
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +210,17 @@ export default async function download(rawUrl, ua, timeout) {
|
|||||||
// 检查订阅有效性
|
// 检查订阅有效性
|
||||||
|
|
||||||
if ($arguments?.validCheck) {
|
if ($arguments?.validCheck) {
|
||||||
await validCheck(parseFlowHeaders(await getFlowHeaders(url)));
|
await validCheck(
|
||||||
|
parseFlowHeaders(
|
||||||
|
await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
proxy,
|
||||||
|
$arguments.flowUrl,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNode) {
|
if (!isNode) {
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import { version as substoreVersion } from '../../package.json';
|
import { version as substoreVersion } from '../../package.json';
|
||||||
import { ENV } from '@/vendor/open-api';
|
import { ENV } from '@/vendor/open-api';
|
||||||
|
|
||||||
const { isNode, isQX, isLoon, isSurge, isStash, isShadowRocket } = ENV();
|
const {
|
||||||
|
isNode,
|
||||||
|
isQX,
|
||||||
|
isLoon,
|
||||||
|
isSurge,
|
||||||
|
isStash,
|
||||||
|
isShadowRocket,
|
||||||
|
isLanceX,
|
||||||
|
isEgern,
|
||||||
|
isGUIforCores,
|
||||||
|
} = ENV();
|
||||||
let backend = 'Node';
|
let backend = 'Node';
|
||||||
if (isNode) backend = 'Node';
|
if (isNode) backend = 'Node';
|
||||||
if (isQX) backend = 'QX';
|
if (isQX) backend = 'QX';
|
||||||
@@ -9,8 +19,49 @@ if (isLoon) backend = 'Loon';
|
|||||||
if (isSurge) backend = 'Surge';
|
if (isSurge) backend = 'Surge';
|
||||||
if (isStash) backend = 'Stash';
|
if (isStash) backend = 'Stash';
|
||||||
if (isShadowRocket) backend = 'ShadowRocket';
|
if (isShadowRocket) backend = 'ShadowRocket';
|
||||||
|
if (isEgern) backend = 'Egern';
|
||||||
|
if (isLanceX) backend = 'LanceX';
|
||||||
|
if (isGUIforCores) backend = 'GUI.for.Cores';
|
||||||
|
|
||||||
|
let meta = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof $environment !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.env = $environment;
|
||||||
|
}
|
||||||
|
if (typeof $loon !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.loon = $loon;
|
||||||
|
}
|
||||||
|
if (typeof $script !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.script = $script;
|
||||||
|
}
|
||||||
|
if (typeof $Plugin !== 'undefined') {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
meta.plugin = $Plugin;
|
||||||
|
}
|
||||||
|
if (isNode) {
|
||||||
|
meta.node = {
|
||||||
|
version: eval('process.version'),
|
||||||
|
argv: eval('process.argv'),
|
||||||
|
filename: eval('__filename'),
|
||||||
|
dirname: eval('__dirname'),
|
||||||
|
env: {},
|
||||||
|
};
|
||||||
|
const env = eval('process.env');
|
||||||
|
for (const key in env) {
|
||||||
|
if (/^SUB_STORE_/.test(key)) {
|
||||||
|
meta.node.env[key] = env[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
backend,
|
backend,
|
||||||
version: substoreVersion,
|
version: substoreVersion,
|
||||||
|
meta,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SETTINGS_KEY } from '@/constants';
|
import { SETTINGS_KEY } from '@/constants';
|
||||||
import { HTTP } from '@/vendor/open-api';
|
import { HTTP, ENV } from '@/vendor/open-api';
|
||||||
|
import { getPolicyDescriptor } from '@/utils';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
import headersResourceCache from '@/utils/headers-resource-cache';
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
|
|
||||||
@@ -9,8 +10,8 @@ export function getFlowField(headers) {
|
|||||||
)[0];
|
)[0];
|
||||||
return headers[subkey];
|
return headers[subkey];
|
||||||
}
|
}
|
||||||
export async function getFlowHeaders(rawUrl, ua, timeout) {
|
export async function getFlowHeaders(rawUrl, ua, timeout, proxy, flowUrl) {
|
||||||
let url = rawUrl;
|
let url = flowUrl || rawUrl;
|
||||||
let $arguments = {};
|
let $arguments = {};
|
||||||
const rawArgs = url.split('#');
|
const rawArgs = url.split('#');
|
||||||
url = url.split('#')[0];
|
url = url.split('#')[0];
|
||||||
@@ -33,6 +34,7 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
|||||||
if ($arguments?.noFlow) {
|
if ($arguments?.noFlow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { isStash, isLoon, isShadowRocket, isQX } = ENV();
|
||||||
const cached = headersResourceCache.get(url);
|
const cached = headersResourceCache.get(url);
|
||||||
let flowInfo;
|
let flowInfo;
|
||||||
if (!$arguments?.noCache && cached) {
|
if (!$arguments?.noCache && cached) {
|
||||||
@@ -46,37 +48,76 @@ export async function getFlowHeaders(rawUrl, ua, timeout) {
|
|||||||
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
'Quantumult%20X/1.0.30 (iPhone14,2; iOS 15.6)';
|
||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const http = HTTP();
|
const http = HTTP();
|
||||||
try {
|
if (flowUrl) {
|
||||||
// $.info(`使用 HEAD 方法获取流量信息: ${url}`);
|
$.info(
|
||||||
const { headers } = await http.head({
|
`使用 GET 方法从响应体获取流量信息: ${flowUrl}, User-Agent: ${
|
||||||
url: url
|
userAgent || ''
|
||||||
.split(/[\r\n]+/)
|
}`,
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)[0],
|
|
||||||
headers: {
|
|
||||||
'User-Agent': userAgent,
|
|
||||||
},
|
|
||||||
timeout: requestTimeout,
|
|
||||||
});
|
|
||||||
flowInfo = getFlowField(headers);
|
|
||||||
} catch (e) {
|
|
||||||
$.error(
|
|
||||||
`使用 HEAD 方法获取流量信息失败: ${url}: ${e.message ?? e}`,
|
|
||||||
);
|
);
|
||||||
}
|
const { body } = await http.get({
|
||||||
if (!flowInfo) {
|
url: flowUrl,
|
||||||
$.info(`使用 GET 方法获取流量信息: ${url}`);
|
|
||||||
const { headers } = await http.get({
|
|
||||||
url: url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)[0],
|
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent,
|
'User-Agent': userAgent,
|
||||||
},
|
},
|
||||||
timeout: requestTimeout,
|
timeout: requestTimeout,
|
||||||
});
|
});
|
||||||
flowInfo = getFlowField(headers);
|
flowInfo = body;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$.info(
|
||||||
|
`使用 HEAD 方法从响应头获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
const { headers } = await http.head({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
...(isStash && proxy
|
||||||
|
? {
|
||||||
|
'X-Stash-Selected-Proxy':
|
||||||
|
encodeURIComponent(proxy),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(isShadowRocket && proxy
|
||||||
|
? { 'X-Surge-Policy': proxy }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
...(proxy ? { proxy } : {}),
|
||||||
|
...(isLoon && proxy ? { node: proxy } : {}),
|
||||||
|
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||||
|
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`使用 HEAD 方法从响应头获取流量信息失败: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}: ${e.message ?? e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!flowInfo) {
|
||||||
|
$.info(
|
||||||
|
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
const { headers } = await http.get({
|
||||||
|
url: url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)[0],
|
||||||
|
headers: {
|
||||||
|
'User-Agent': userAgent,
|
||||||
|
},
|
||||||
|
timeout: requestTimeout,
|
||||||
|
});
|
||||||
|
flowInfo = getFlowField(headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (flowInfo) {
|
if (flowInfo) {
|
||||||
headersResourceCache.set(url, flowInfo);
|
headersResourceCache.set(url, flowInfo);
|
||||||
@@ -113,10 +154,10 @@ export function parseFlowHeaders(flowHeaders) {
|
|||||||
return { expires, total, usage: { upload, download } };
|
return { expires, total, usage: { upload, download } };
|
||||||
}
|
}
|
||||||
export function flowTransfer(flow, unit = 'B') {
|
export function flowTransfer(flow, unit = 'B') {
|
||||||
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
let unitIndex = unitList.indexOf(unit);
|
let unitIndex = unitList.indexOf(unit);
|
||||||
|
|
||||||
return flow < 1024
|
return flow < 1024 || unitIndex === unitList.length - 1
|
||||||
? { value: flow.toFixed(1), unit: unit }
|
? { value: flow.toFixed(1), unit: unit }
|
||||||
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
||||||
}
|
}
|
||||||
@@ -143,3 +184,60 @@ export function validCheck(flow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRmainingDays(opt = {}) {
|
||||||
|
try {
|
||||||
|
let { resetDay, startDate, cycleDays } = opt;
|
||||||
|
if (['string', 'number'].includes(typeof opt)) {
|
||||||
|
resetDay = opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate && cycleDays) {
|
||||||
|
cycleDays = parseInt(cycleDays);
|
||||||
|
if (isNaN(cycleDays) || cycleDays <= 0)
|
||||||
|
throw new Error('重置周期应为正整数');
|
||||||
|
if (!startDate || !Date.parse(startDate))
|
||||||
|
throw new Error('开始日期不合法');
|
||||||
|
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const today = new Date();
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
if (start.getTime() > today.getTime())
|
||||||
|
throw new Error('开始日期应早于现在');
|
||||||
|
|
||||||
|
let resetDate = new Date(startDate);
|
||||||
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
||||||
|
|
||||||
|
while (resetDate < today) {
|
||||||
|
resetDate.setDate(resetDate.getDate() + cycleDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDate.setHours(0, 0, 0, 0);
|
||||||
|
const timeDiff = resetDate.getTime() - today.getTime();
|
||||||
|
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||||
|
|
||||||
|
return daysDiff;
|
||||||
|
} else {
|
||||||
|
if (!resetDay) return;
|
||||||
|
resetDay = parseInt(resetDay);
|
||||||
|
if (isNaN(resetDay) || resetDay <= 0 || resetDay > 31)
|
||||||
|
throw new Error('月重置日应为 1-31 之间的整数');
|
||||||
|
let now = new Date();
|
||||||
|
let today = now.getDate();
|
||||||
|
let month = now.getMonth();
|
||||||
|
let year = now.getFullYear();
|
||||||
|
let daysInMonth;
|
||||||
|
|
||||||
|
if (resetDay > today) {
|
||||||
|
daysInMonth = 0;
|
||||||
|
} else {
|
||||||
|
daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysInMonth - today + resetDay;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$.error(`getRmainingDays failed: ${e.message ?? e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,107 @@
|
|||||||
|
import $ from '@/core/app';
|
||||||
|
|
||||||
|
const ISOFlags = {
|
||||||
|
'🏳️🌈': ['EXP', 'BAND'],
|
||||||
|
'🇸🇱': ['TEST', 'SOS'],
|
||||||
|
'🇦🇩': ['AD', 'AND'],
|
||||||
|
'🇦🇪': ['AE', 'ARE'],
|
||||||
|
'🇦🇫': ['AF', 'AFG'],
|
||||||
|
'🇦🇱': ['AL', 'ALB'],
|
||||||
|
'🇦🇲': ['AM', 'ARM'],
|
||||||
|
'🇦🇷': ['AR', 'ARG'],
|
||||||
|
'🇦🇹': ['AT', 'AUT'],
|
||||||
|
'🇦🇺': ['AU', 'AUS'],
|
||||||
|
'🇦🇿': ['AZ', 'AZE'],
|
||||||
|
'🇧🇦': ['BA', 'BIH'],
|
||||||
|
'🇧🇩': ['BD', 'BGD'],
|
||||||
|
'🇧🇪': ['BE', 'BEL'],
|
||||||
|
'🇧🇬': ['BG', 'BGR'],
|
||||||
|
'🇧🇭': ['BH', 'BHR'],
|
||||||
|
'🇧🇷': ['BR', 'BRA'],
|
||||||
|
'🇧🇾': ['BY', 'BLR'],
|
||||||
|
'🇨🇦': ['CA', 'CAN'],
|
||||||
|
'🇨🇭': ['CH', 'CHE'],
|
||||||
|
'🇨🇱': ['CL', 'CHL'],
|
||||||
|
'🇨🇴': ['CO', 'COL'],
|
||||||
|
'🇨🇷': ['CR', 'CRI'],
|
||||||
|
'🇨🇾': ['CY', 'CYP'],
|
||||||
|
'🇨🇿': ['CZ', 'CZE'],
|
||||||
|
'🇩🇪': ['DE', 'DEU'],
|
||||||
|
'🇩🇰': ['DK', 'DNK'],
|
||||||
|
'🇪🇨': ['EC', 'ECU'],
|
||||||
|
'🇪🇪': ['EE', 'EST'],
|
||||||
|
'🇪🇬': ['EG', 'EGY'],
|
||||||
|
'🇪🇸': ['ES', 'ESP'],
|
||||||
|
'🇪🇺': ['EU'],
|
||||||
|
'🇫🇮': ['FI', 'FIN'],
|
||||||
|
'🇫🇷': ['FR', 'FRA'],
|
||||||
|
'🇬🇧': ['GB', 'GBR', 'UK'],
|
||||||
|
'🇬🇪': ['GE', 'GEO'],
|
||||||
|
'🇬🇷': ['GR', 'GRC'],
|
||||||
|
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||||
|
'🇭🇷': ['HR', 'HRV'],
|
||||||
|
'🇭🇺': ['HU', 'HUN'],
|
||||||
|
'🇯🇴': ['JO', 'JOR'],
|
||||||
|
'🇯🇵': ['JP', 'JPN', 'TYO'],
|
||||||
|
'🇰🇪': ['KE', 'KEN'],
|
||||||
|
'🇰🇬': ['KG', 'KGZ'],
|
||||||
|
'🇰🇭': ['KH', 'KGZ'],
|
||||||
|
'🇰🇵': ['KP', 'PRK'],
|
||||||
|
'🇰🇷': ['KR', 'KOR', 'SEL'],
|
||||||
|
'🇰🇿': ['KZ', 'KAZ'],
|
||||||
|
'🇮🇩': ['ID', 'IDN'],
|
||||||
|
'🇮🇪': ['IE', 'IRL'],
|
||||||
|
'🇮🇱': ['IL', 'ISR'],
|
||||||
|
'🇮🇲': ['IM', 'IMN'],
|
||||||
|
'🇮🇳': ['IN', 'IND'],
|
||||||
|
'🇮🇷': ['IR', 'IRN'],
|
||||||
|
'🇮🇸': ['IS', 'ISL'],
|
||||||
|
'🇮🇹': ['IT', 'ITA'],
|
||||||
|
'🇱🇹': ['LT', 'LTU'],
|
||||||
|
'🇱🇺': ['LU', 'LUX'],
|
||||||
|
'🇱🇻': ['LV', 'LVA'],
|
||||||
|
'🇲🇦': ['MA', 'MAR'],
|
||||||
|
'🇲🇩': ['MD', 'MDA'],
|
||||||
|
'🇳🇬': ['NG', 'NGA'],
|
||||||
|
'🇲🇰': ['MK', 'MKD'],
|
||||||
|
'🇲🇳': ['MN', 'MNG'],
|
||||||
|
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
||||||
|
'🇲🇹': ['MT', 'MLT'],
|
||||||
|
'🇲🇽': ['MX', 'MEX'],
|
||||||
|
'🇲🇾': ['MY', 'MYS'],
|
||||||
|
'🇳🇱': ['NL', 'NLD', 'AMS'],
|
||||||
|
'🇳🇴': ['NO', 'NOR'],
|
||||||
|
'🇳🇵': ['NP', 'NPL'],
|
||||||
|
'🇳🇿': ['NZ', 'NZL'],
|
||||||
|
'🇵🇦': ['PA', 'PAN'],
|
||||||
|
'🇵🇪': ['PE', 'PER'],
|
||||||
|
'🇵🇭': ['PH', 'PHL'],
|
||||||
|
'🇵🇰': ['PK', 'PAK'],
|
||||||
|
'🇵🇱': ['PL', 'POL'],
|
||||||
|
'🇵🇷': ['PR', 'PRI'],
|
||||||
|
'🇵🇹': ['PT', 'PRT'],
|
||||||
|
'🇵🇾': ['PY', 'PRY'],
|
||||||
|
'🇷🇴': ['RO', 'ROU'],
|
||||||
|
'🇷🇸': ['RS', 'SRB'],
|
||||||
|
'🇷🇪': ['RE', 'REU'],
|
||||||
|
'🇷🇺': ['RU', 'RUS'],
|
||||||
|
'🇸🇦': ['SA', 'SAU'],
|
||||||
|
'🇸🇪': ['SE', 'SWE'],
|
||||||
|
'🇸🇬': ['SG', 'SGP'],
|
||||||
|
'🇸🇮': ['SI', 'SVN'],
|
||||||
|
'🇸🇰': ['SK', 'SVK'],
|
||||||
|
'🇹🇭': ['TH', 'THA'],
|
||||||
|
'🇹🇳': ['TN', 'TUN'],
|
||||||
|
'🇹🇷': ['TR', 'TUR'],
|
||||||
|
'🇹🇼': ['TW', 'TWN', 'CHT', 'HINET', 'ROC'],
|
||||||
|
'🇺🇦': ['UA', 'UKR'],
|
||||||
|
'🇺🇸': ['US', 'USA', 'LAX', 'SFO', 'SJC'],
|
||||||
|
'🇺🇾': ['UY', 'URY'],
|
||||||
|
'🇻🇪': ['VE', 'VEN'],
|
||||||
|
'🇻🇳': ['VN', 'VNM'],
|
||||||
|
'🇿🇦': ['ZA', 'ZAF', 'JNB'],
|
||||||
|
'🇨🇳': ['CN', 'CHN', 'BACK'],
|
||||||
|
};
|
||||||
// get proxy flag according to its name
|
// get proxy flag according to its name
|
||||||
export function getFlag(name) {
|
export function getFlag(name) {
|
||||||
// flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js
|
// flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js
|
||||||
@@ -65,6 +169,7 @@ export function getFlag(name) {
|
|||||||
'广德',
|
'广德',
|
||||||
'法兰克福',
|
'法兰克福',
|
||||||
'Frankfurt',
|
'Frankfurt',
|
||||||
|
'德意志',
|
||||||
],
|
],
|
||||||
'🇩🇰': ['Denmark', '丹麦', '丹麥'],
|
'🇩🇰': ['Denmark', '丹麦', '丹麥'],
|
||||||
'🇪🇨': ['Ecuador', '厄瓜多尔'],
|
'🇪🇨': ['Ecuador', '厄瓜多尔'],
|
||||||
@@ -213,11 +318,16 @@ export function getFlag(name) {
|
|||||||
'🇹🇼': [
|
'🇹🇼': [
|
||||||
'Taiwan',
|
'Taiwan',
|
||||||
'台湾',
|
'台湾',
|
||||||
|
'臺灣',
|
||||||
|
'台灣',
|
||||||
|
'中華民國',
|
||||||
|
'中华民国',
|
||||||
'台北',
|
'台北',
|
||||||
'台中',
|
'台中',
|
||||||
'新北',
|
'新北',
|
||||||
'彰化',
|
'彰化',
|
||||||
'台',
|
'台',
|
||||||
|
'臺',
|
||||||
'Taipei',
|
'Taipei',
|
||||||
],
|
],
|
||||||
'🇺🇦': ['Ukraine', '乌克兰', '烏克蘭'],
|
'🇺🇦': ['Ukraine', '乌克兰', '烏克蘭'],
|
||||||
@@ -278,108 +388,6 @@ export function getFlag(name) {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ISOFlags = {
|
|
||||||
'🏳️🌈': ['EXP', 'BAND'],
|
|
||||||
'🇸🇱': ['TEST', 'SOS'],
|
|
||||||
'🇦🇩': ['AD', 'AND'],
|
|
||||||
'🇦🇪': ['AE', 'ARE'],
|
|
||||||
'🇦🇫': ['AF', 'AFG'],
|
|
||||||
'🇦🇱': ['AL', 'ALB'],
|
|
||||||
'🇦🇲': ['AM', 'ARM'],
|
|
||||||
'🇦🇷': ['AR', 'ARG'],
|
|
||||||
'🇦🇹': ['AT', 'AUT'],
|
|
||||||
'🇦🇺': ['AU', 'AUS'],
|
|
||||||
'🇦🇿': ['AZ', 'AZE'],
|
|
||||||
'🇧🇦': ['BA', 'BIH'],
|
|
||||||
'🇧🇩': ['BD', 'BGD'],
|
|
||||||
'🇧🇪': ['BE', 'BEL'],
|
|
||||||
'🇧🇬': ['BG', 'BGR'],
|
|
||||||
'🇧🇭': ['BH', 'BHR'],
|
|
||||||
'🇧🇷': ['BR', 'BRA'],
|
|
||||||
'🇧🇾': ['BY', 'BLR'],
|
|
||||||
'🇨🇦': ['CA', 'CAN'],
|
|
||||||
'🇨🇭': ['CH', 'CHE'],
|
|
||||||
'🇨🇱': ['CL', 'CHL'],
|
|
||||||
'🇨🇴': ['CO', 'COL'],
|
|
||||||
'🇨🇷': ['CR', 'CRI'],
|
|
||||||
'🇨🇾': ['CY', 'CYP'],
|
|
||||||
'🇨🇿': ['CZ', 'CZE'],
|
|
||||||
'🇩🇪': ['DE', 'DEU'],
|
|
||||||
'🇩🇰': ['DK', 'DNK'],
|
|
||||||
'🇪🇨': ['EC', 'ECU'],
|
|
||||||
'🇪🇪': ['EE', 'EST'],
|
|
||||||
'🇪🇬': ['EG', 'EGY'],
|
|
||||||
'🇪🇸': ['ES', 'ESP'],
|
|
||||||
'🇪🇺': ['EU'],
|
|
||||||
'🇫🇮': ['FI', 'FIN'],
|
|
||||||
'🇫🇷': ['FR', 'FRA'],
|
|
||||||
'🇬🇧': ['GB', 'GBR', 'UK'],
|
|
||||||
'🇬🇪': ['GE', 'GEO'],
|
|
||||||
'🇬🇷': ['GR', 'GRC'],
|
|
||||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
|
||||||
'🇭🇷': ['HR', 'HRV'],
|
|
||||||
'🇭🇺': ['HU', 'HUN'],
|
|
||||||
'🇯🇴': ['JO', 'JOR'],
|
|
||||||
'🇯🇵': ['JP', 'JPN'],
|
|
||||||
'🇰🇪': ['KE', 'KEN'],
|
|
||||||
'🇰🇬': ['KG', 'KGZ'],
|
|
||||||
'🇰🇭': ['KH', 'KGZ'],
|
|
||||||
'🇰🇵': ['KP', 'PRK'],
|
|
||||||
'🇰🇷': ['KR', 'KOR'],
|
|
||||||
'🇰🇿': ['KZ', 'KAZ'],
|
|
||||||
'🇮🇩': ['ID', 'IDN'],
|
|
||||||
'🇮🇪': ['IE', 'IRL'],
|
|
||||||
'🇮🇱': ['IL', 'ISR'],
|
|
||||||
'🇮🇲': ['IM', 'IMN'],
|
|
||||||
'🇮🇳': ['IN', 'IND'],
|
|
||||||
'🇮🇷': ['IR', 'IRN'],
|
|
||||||
'🇮🇸': ['IS', 'ISL'],
|
|
||||||
'🇮🇹': ['IT', 'ITA'],
|
|
||||||
'🇱🇹': ['LT', 'LTU'],
|
|
||||||
'🇱🇺': ['LU', 'LUX'],
|
|
||||||
'🇱🇻': ['LV', 'LVA'],
|
|
||||||
'🇲🇦': ['MA', 'MAR'],
|
|
||||||
'🇲🇩': ['MD', 'MDA'],
|
|
||||||
'🇳🇬': ['NG', 'NGA'],
|
|
||||||
'🇲🇰': ['MK', 'MKD'],
|
|
||||||
'🇲🇳': ['MN', 'MNG'],
|
|
||||||
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
|
||||||
'🇲🇹': ['MT', 'MLT'],
|
|
||||||
'🇲🇽': ['MX', 'MEX'],
|
|
||||||
'🇲🇾': ['MY', 'MYS'],
|
|
||||||
'🇳🇱': ['NL', 'NLD'],
|
|
||||||
'🇳🇴': ['NO', 'NOR'],
|
|
||||||
'🇳🇵': ['NP', 'NPL'],
|
|
||||||
'🇳🇿': ['NZ', 'NZL'],
|
|
||||||
'🇵🇦': ['PA', 'PAN'],
|
|
||||||
'🇵🇪': ['PE', 'PER'],
|
|
||||||
'🇵🇭': ['PH', 'PHL'],
|
|
||||||
'🇵🇰': ['PK', 'PAK'],
|
|
||||||
'🇵🇱': ['PL', 'POL'],
|
|
||||||
'🇵🇷': ['PR', 'PRI'],
|
|
||||||
'🇵🇹': ['PT', 'PRT'],
|
|
||||||
'🇵🇾': ['PY', 'PRY'],
|
|
||||||
'🇷🇴': ['RO', 'ROU'],
|
|
||||||
'🇷🇸': ['RS', 'SRB'],
|
|
||||||
'🇷🇪': ['RE', 'REU'],
|
|
||||||
'🇷🇺': ['RU', 'RUS'],
|
|
||||||
'🇸🇦': ['SA', 'SAU'],
|
|
||||||
'🇸🇪': ['SE', 'SWE'],
|
|
||||||
'🇸🇬': ['SG', 'SGP'],
|
|
||||||
'🇸🇮': ['SI', 'SVN'],
|
|
||||||
'🇸🇰': ['SK', 'SVK'],
|
|
||||||
'🇹🇭': ['TH', 'THA'],
|
|
||||||
'🇹🇳': ['TN', 'TUN'],
|
|
||||||
'🇹🇷': ['TR', 'TUR'],
|
|
||||||
'🇹🇼': ['TW', 'TWN', 'CHT', 'HINET'],
|
|
||||||
'🇺🇦': ['UA', 'UKR'],
|
|
||||||
'🇺🇸': ['US', 'USA', 'LAX', 'SFO'],
|
|
||||||
'🇺🇾': ['UY', 'URY'],
|
|
||||||
'🇻🇪': ['VE', 'VEN'],
|
|
||||||
'🇻🇳': ['VN', 'VNM'],
|
|
||||||
'🇿🇦': ['ZA', 'ZAF'],
|
|
||||||
'🇨🇳': ['CN', 'CHN', 'BACK'],
|
|
||||||
};
|
|
||||||
// 原旗帜或空
|
// 原旗帜或空
|
||||||
let Flag =
|
let Flag =
|
||||||
name.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)?.[0] ||
|
name.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)?.[0] ||
|
||||||
@@ -393,7 +401,9 @@ export function getFlag(name) {
|
|||||||
// 不精确匹配(只要包含就算,忽略大小写)
|
// 不精确匹配(只要包含就算,忽略大小写)
|
||||||
keywords.some((keyword) => RegExp(`${keyword}`, 'i').test(name))
|
keywords.some((keyword) => RegExp(`${keyword}`, 'i').test(name))
|
||||||
) {
|
) {
|
||||||
//console.log(`newFlag = ${flag}`)
|
if (/内蒙古/.test(name) && ['🇲🇳'].includes(flag)) {
|
||||||
|
return (Flag = '🇨🇳');
|
||||||
|
}
|
||||||
return (Flag = flag);
|
return (Flag = flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +421,56 @@ export function getFlag(name) {
|
|||||||
return (Flag = flag);
|
return (Flag = flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(`Final Flag = ${Flag}`)
|
//console.log(`Final Flag = ${Flag}`)
|
||||||
return Flag;
|
return Flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getISO(name) {
|
||||||
|
return ISOFlags[getFlag(name)]?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove flag
|
||||||
|
export function removeFlag(str) {
|
||||||
|
return str
|
||||||
|
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]|🏴☠️|🏳️🌈/g, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MMDB {
|
||||||
|
constructor({ country, asn } = {}) {
|
||||||
|
if ($.env.isNode) {
|
||||||
|
const Reader = eval(`require("@maxmind/geoip2-node")`).Reader;
|
||||||
|
const fs = eval("require('fs')");
|
||||||
|
const countryFile =
|
||||||
|
country || eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH');
|
||||||
|
const asnFile = asn || eval('process.env.SUB_STORE_MMDB_ASN_PATH');
|
||||||
|
// $.info(
|
||||||
|
// `GeoLite2 Country MMDB: ${countryFile}, exists: ${fs.existsSync(
|
||||||
|
// countryFile,
|
||||||
|
// )}`,
|
||||||
|
// );
|
||||||
|
if (countryFile) {
|
||||||
|
this.countryReader = Reader.openBuffer(
|
||||||
|
fs.readFileSync(countryFile),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// $.info(
|
||||||
|
// `GeoLite2 ASN MMDB: ${asnFile}, exists: ${fs.existsSync(
|
||||||
|
// asnFile,
|
||||||
|
// )}`,
|
||||||
|
// );
|
||||||
|
if (asnFile) {
|
||||||
|
if (!fs.existsSync(asnFile))
|
||||||
|
throw new Error('GeoLite2 ASN MMDB does not exist');
|
||||||
|
this.asnReader = Reader.openBuffer(fs.readFileSync(asnFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoip(ip) {
|
||||||
|
return this.countryReader?.country(ip)?.country?.isoCode;
|
||||||
|
}
|
||||||
|
ipaso(ip) {
|
||||||
|
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ export default class Gist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(`result`, result);
|
// console.log(`result`, result);
|
||||||
console.log(`files`, files);
|
// console.log(`files`, files);
|
||||||
console.log(`actions`, actions);
|
// console.log(`actions`, actions);
|
||||||
|
|
||||||
if (this.syncPlatform === 'gitlab') {
|
if (this.syncPlatform === 'gitlab') {
|
||||||
if (Object.keys(result).length === 0) {
|
if (Object.keys(result).length === 0) {
|
||||||
|
|||||||
@@ -10,7 +10,17 @@ class ResourceCache {
|
|||||||
if (!$.read(HEADERS_RESOURCE_CACHE_KEY)) {
|
if (!$.read(HEADERS_RESOURCE_CACHE_KEY)) {
|
||||||
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
|
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
|
||||||
}
|
}
|
||||||
this.resourceCache = JSON.parse($.read(HEADERS_RESOURCE_CACHE_KEY));
|
try {
|
||||||
|
this.resourceCache = JSON.parse($.read(HEADERS_RESOURCE_CACHE_KEY));
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`解析持久化缓存中的 ${HEADERS_RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
|
||||||
|
e?.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
this.resourceCache = {};
|
||||||
|
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,64 @@ function getIfPresent(obj, defaultValue) {
|
|||||||
return isPresent(obj) ? obj : defaultValue;
|
return isPresent(obj) ? obj : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPolicyDescriptor(str) {
|
||||||
|
if (!str) return {};
|
||||||
|
return /^.+?\s*?=\s*?.+?\s*?,.+?/.test(str)
|
||||||
|
? {
|
||||||
|
'policy-descriptor': str,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
policy: str,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const utf8ArrayToStr =
|
||||||
|
typeof TextDecoder !== 'undefined'
|
||||||
|
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||||
|
: (function () {
|
||||||
|
var charCache = new Array(128); // Preallocate the cache for the common single byte chars
|
||||||
|
var charFromCodePt = String.fromCodePoint || String.fromCharCode;
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
return function (array) {
|
||||||
|
var codePt, byte1;
|
||||||
|
var buffLen = array.length;
|
||||||
|
|
||||||
|
result.length = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < buffLen; ) {
|
||||||
|
byte1 = array[i++];
|
||||||
|
|
||||||
|
if (byte1 <= 0x7f) {
|
||||||
|
codePt = byte1;
|
||||||
|
} else if (byte1 <= 0xdf) {
|
||||||
|
codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
|
||||||
|
} else if (byte1 <= 0xef) {
|
||||||
|
codePt =
|
||||||
|
((byte1 & 0x0f) << 12) |
|
||||||
|
((array[i++] & 0x3f) << 6) |
|
||||||
|
(array[i++] & 0x3f);
|
||||||
|
} else if (String.fromCodePoint) {
|
||||||
|
codePt =
|
||||||
|
((byte1 & 0x07) << 18) |
|
||||||
|
((array[i++] & 0x3f) << 12) |
|
||||||
|
((array[i++] & 0x3f) << 6) |
|
||||||
|
(array[i++] & 0x3f);
|
||||||
|
} else {
|
||||||
|
codePt = 63; // Cannot convert four byte code points, so use "?" instead
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(
|
||||||
|
charCache[codePt] ||
|
||||||
|
(charCache[codePt] = charFromCodePt(codePt)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join('');
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
@@ -43,4 +101,6 @@ export {
|
|||||||
getIfNotBlank,
|
getIfNotBlank,
|
||||||
isPresent,
|
isPresent,
|
||||||
getIfPresent,
|
getIfPresent,
|
||||||
|
utf8ArrayToStr,
|
||||||
|
getPolicyDescriptor,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,17 @@ class ResourceCache {
|
|||||||
if (!$.read(RESOURCE_CACHE_KEY)) {
|
if (!$.read(RESOURCE_CACHE_KEY)) {
|
||||||
$.write('{}', RESOURCE_CACHE_KEY);
|
$.write('{}', RESOURCE_CACHE_KEY);
|
||||||
}
|
}
|
||||||
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
|
try {
|
||||||
|
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`解析持久化缓存中的 ${RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
|
||||||
|
e?.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
this.resourceCache = {};
|
||||||
|
$.write('{}', RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,17 @@ class ResourceCache {
|
|||||||
if (!$.read(SCRIPT_RESOURCE_CACHE_KEY)) {
|
if (!$.read(SCRIPT_RESOURCE_CACHE_KEY)) {
|
||||||
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
|
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
|
||||||
}
|
}
|
||||||
this.resourceCache = JSON.parse($.read(SCRIPT_RESOURCE_CACHE_KEY));
|
try {
|
||||||
|
this.resourceCache = JSON.parse($.read(SCRIPT_RESOURCE_CACHE_KEY));
|
||||||
|
} catch (e) {
|
||||||
|
$.error(
|
||||||
|
`解析持久化缓存中的 ${SCRIPT_RESOURCE_CACHE_KEY} 失败, 重置为 {}, 错误: ${
|
||||||
|
e?.message ?? e
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
this.resourceCache = {};
|
||||||
|
$.write('{}', SCRIPT_RESOURCE_CACHE_KEY);
|
||||||
|
}
|
||||||
this._cleanup();
|
this._cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function getPlatformFromHeaders(headers) {
|
export function getUserAgentFromHeaders(headers) {
|
||||||
const keys = Object.keys(headers);
|
const keys = Object.keys(headers);
|
||||||
let UA = '';
|
let UA = '';
|
||||||
let ua = '';
|
let ua = '';
|
||||||
@@ -9,6 +9,9 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { UA, ua };
|
||||||
|
}
|
||||||
|
export function getPlatformFromUserAgent({ ua, UA }) {
|
||||||
if (UA.indexOf('Quantumult%20X') !== -1) {
|
if (UA.indexOf('Quantumult%20X') !== -1) {
|
||||||
return 'QX';
|
return 'QX';
|
||||||
} else if (UA.indexOf('Surfboard') !== -1) {
|
} else if (UA.indexOf('Surfboard') !== -1) {
|
||||||
@@ -38,3 +41,7 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
return 'JSON';
|
return 'JSON';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function getPlatformFromHeaders(headers) {
|
||||||
|
const { UA, ua } = getUserAgentFromHeaders(headers);
|
||||||
|
return getPlatformFromUserAgent({ ua, UA });
|
||||||
|
}
|
||||||
4
backend/src/vendor/express.js
vendored
4
backend/src/vendor/express.js
vendored
@@ -161,7 +161,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
|
|
||||||
function Response() {
|
function Response() {
|
||||||
let statusCode = 200;
|
let statusCode = 200;
|
||||||
const { isQX, isLoon, isSurge } = ENV();
|
const { isQX, isLoon, isSurge, isGUIforCores } = ENV();
|
||||||
const headers = DEFAULT_HEADERS;
|
const headers = DEFAULT_HEADERS;
|
||||||
const STATUS_CODE_MAP = {
|
const STATUS_CODE_MAP = {
|
||||||
200: 'HTTP/1.1 200 OK',
|
200: 'HTTP/1.1 200 OK',
|
||||||
@@ -184,7 +184,7 @@ export default function express({ substore: $, port, host }) {
|
|||||||
body,
|
body,
|
||||||
headers,
|
headers,
|
||||||
};
|
};
|
||||||
if (isQX) {
|
if (isQX || isGUIforCores) {
|
||||||
$done(response);
|
$done(response);
|
||||||
} else if (isLoon || isSurge) {
|
} else if (isLoon || isSurge) {
|
||||||
$done({
|
$done({
|
||||||
|
|||||||
118
backend/src/vendor/open-api.js
vendored
118
backend/src/vendor/open-api.js
vendored
@@ -6,6 +6,9 @@ const isNode = eval(`typeof process !== "undefined"`); // eval is needed in orde
|
|||||||
const isStash =
|
const isStash =
|
||||||
'undefined' !== typeof $environment && $environment['stash-version'];
|
'undefined' !== typeof $environment && $environment['stash-version'];
|
||||||
const isShadowRocket = 'undefined' !== typeof $rocket;
|
const isShadowRocket = 'undefined' !== typeof $rocket;
|
||||||
|
const isEgern = 'object' == typeof egern;
|
||||||
|
const isLanceX = 'undefined' != typeof $native;
|
||||||
|
const isGUIforCores = typeof $Plugins !== 'undefined';
|
||||||
|
|
||||||
export class OpenAPI {
|
export class OpenAPI {
|
||||||
constructor(name = 'untitled', debug = false) {
|
constructor(name = 'untitled', debug = false) {
|
||||||
@@ -46,7 +49,10 @@ export class OpenAPI {
|
|||||||
this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}');
|
this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}');
|
||||||
if (isLoon || isSurge)
|
if (isLoon || isSurge)
|
||||||
this.cache = JSON.parse($persistentStore.read(this.name) || '{}');
|
this.cache = JSON.parse($persistentStore.read(this.name) || '{}');
|
||||||
|
if (isGUIforCores)
|
||||||
|
this.cache = JSON.parse(
|
||||||
|
$Plugins.SubStoreCache.get(this.name) || '{}',
|
||||||
|
);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
// create a json for root cache
|
// create a json for root cache
|
||||||
const basePath =
|
const basePath =
|
||||||
@@ -84,6 +90,7 @@ export class OpenAPI {
|
|||||||
const data = JSON.stringify(this.cache, null, 2);
|
const data = JSON.stringify(this.cache, null, 2);
|
||||||
if (isQX) $prefs.setValueForKey(data, this.name);
|
if (isQX) $prefs.setValueForKey(data, this.name);
|
||||||
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
if (isLoon || isSurge) $persistentStore.write(data, this.name);
|
||||||
|
if (isGUIforCores) $Plugins.SubStoreCache.set(this.name, data);
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const basePath =
|
const basePath =
|
||||||
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
eval('process.env.SUB_STORE_DATA_BASE_PATH') || '.';
|
||||||
@@ -116,6 +123,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
this.root[key] = data;
|
this.root[key] = data;
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.set(key, data);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.cache[key] = data;
|
this.cache[key] = data;
|
||||||
}
|
}
|
||||||
@@ -135,6 +145,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
return this.root[key];
|
return this.root[key];
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.get(key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.cache[key];
|
return this.cache[key];
|
||||||
}
|
}
|
||||||
@@ -153,6 +166,9 @@ export class OpenAPI {
|
|||||||
if (isNode) {
|
if (isNode) {
|
||||||
delete this.root[key];
|
delete this.root[key];
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
return $Plugins.SubStoreCache.remove(key);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete this.cache[key];
|
delete this.cache[key];
|
||||||
}
|
}
|
||||||
@@ -218,6 +234,9 @@ export class OpenAPI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isGUIforCores) {
|
||||||
|
$Plugins.Notify(title, subtitle + '\n' + content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// other helper functions
|
// other helper functions
|
||||||
@@ -238,7 +257,7 @@ export class OpenAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
done(value = {}) {
|
done(value = {}) {
|
||||||
if (isQX || isLoon || isSurge) {
|
if (isQX || isLoon || isSurge || isGUIforCores) {
|
||||||
$done(value);
|
$done(value);
|
||||||
} else if (isNode) {
|
} else if (isNode) {
|
||||||
if (typeof $context !== 'undefined') {
|
if (typeof $context !== 'undefined') {
|
||||||
@@ -251,11 +270,21 @@ export class OpenAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ENV() {
|
export function ENV() {
|
||||||
return { isQX, isLoon, isSurge, isNode, isStash, isShadowRocket };
|
return {
|
||||||
|
isQX,
|
||||||
|
isLoon,
|
||||||
|
isSurge,
|
||||||
|
isNode,
|
||||||
|
isStash,
|
||||||
|
isShadowRocket,
|
||||||
|
isEgern,
|
||||||
|
isLanceX,
|
||||||
|
isGUIforCores,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HTTP(defaultOptions = { baseURL: '' }) {
|
export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||||
const { isQX, isLoon, isSurge, isNode } = ENV();
|
const { isQX, isLoon, isSurge, isNode, isGUIforCores } = ENV();
|
||||||
const methods = [
|
const methods = [
|
||||||
'GET',
|
'GET',
|
||||||
'POST',
|
'POST',
|
||||||
@@ -305,42 +334,77 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
url: options.url,
|
url: options.url,
|
||||||
headers: options.headers,
|
headers: options.headers,
|
||||||
body: options.body,
|
body: options.body,
|
||||||
|
opts: options.opts,
|
||||||
});
|
});
|
||||||
} else if (isLoon || isSurge || isNode) {
|
} else if (isLoon || isSurge || isNode) {
|
||||||
worker = new Promise((resolve, reject) => {
|
worker = new Promise((resolve, reject) => {
|
||||||
const request = isNode
|
const request = isNode
|
||||||
? eval("require('request')")
|
? eval("require('request')")
|
||||||
: $httpClient;
|
: $httpClient;
|
||||||
request[method.toLowerCase()](
|
const opts = JSON.parse(JSON.stringify(options));
|
||||||
options,
|
if (!isNode && opts.timeout) {
|
||||||
(err, response, body) => {
|
opts.timeout++;
|
||||||
// if (err) {
|
let unit = 'ms';
|
||||||
// console.log(err);
|
// 这些客户端单位为 s
|
||||||
// } else {
|
if (isSurge || isStash || isShadowRocket) {
|
||||||
// console.log({
|
opts.timeout = Math.ceil(opts.timeout / 1000);
|
||||||
// statusCode:
|
unit = 's';
|
||||||
// response.status || response.statusCode,
|
}
|
||||||
// headers: response.headers,
|
// Loon 为 ms
|
||||||
// body,
|
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
|
||||||
// });
|
}
|
||||||
// }
|
request[method.toLowerCase()](opts, (err, response, body) => {
|
||||||
|
// if (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// } else {
|
||||||
|
// console.log({
|
||||||
|
// statusCode:
|
||||||
|
// response.status || response.statusCode,
|
||||||
|
// headers: response.headers,
|
||||||
|
// body,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else
|
else
|
||||||
resolve({
|
resolve({
|
||||||
statusCode:
|
statusCode: response.status || response.statusCode,
|
||||||
response.status || response.statusCode,
|
headers: response.headers,
|
||||||
headers: response.headers,
|
body,
|
||||||
body,
|
});
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
} else if (isGUIforCores) {
|
||||||
|
worker = new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const response = await $Plugins.Requests({
|
||||||
|
method,
|
||||||
|
url: options.url,
|
||||||
|
headers: options.headers,
|
||||||
|
body: options.body,
|
||||||
|
options: {
|
||||||
|
Proxy: options.proxy,
|
||||||
|
Timeout: options.timeout
|
||||||
|
? options.timeout / 1000
|
||||||
|
: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve({
|
||||||
|
statusCode: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
body: response.body,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutid;
|
let timeoutid;
|
||||||
|
|
||||||
const timer = timeout
|
const timer = timeout
|
||||||
? new Promise((_, reject) => {
|
? new Promise((_, reject) => {
|
||||||
|
// console.log(`[request timeout] ${timeout}ms`);
|
||||||
timeoutid = setTimeout(() => {
|
timeoutid = setTimeout(() => {
|
||||||
events.onTimeout();
|
events.onTimeout();
|
||||||
return reject(
|
return reject(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具. 定时任务默认为每天 0 点
|
#!desc=高级订阅管理工具. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!openUrl=https://sub.store
|
#!openUrl=https://sub.store
|
||||||
#!author=Peng-YM
|
#!author=Peng-YM
|
||||||
#!homepage=https://github.com/sub-store-org/Sub-Store
|
#!homepage=https://github.com/sub-store-org/Sub-Store
|
||||||
@@ -17,4 +17,4 @@ hostname=sub.store
|
|||||||
http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core
|
http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core
|
||||||
http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
|
||||||
|
|
||||||
cron "0 0 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync
|
cron "55 23 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, timeout=120, tag=Sub-Store Sync
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Sub-Store",
|
"name": "Sub-Store",
|
||||||
"description": "定时任务默认为每天 0 点",
|
"description": "定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'",
|
||||||
"task": [
|
"task": [
|
||||||
"0 0 * * * https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"
|
"55 23 * * * https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -6,20 +6,32 @@ Sub-Store Releases: [`https://github.com/sub-store-org/Sub-Store/releases`](http
|
|||||||
|
|
||||||
Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
||||||
|
|
||||||
## 脚本配置:
|
## 服务器/云平台/Docker/Android 版
|
||||||
|
|
||||||
|
https://xream.notion.site/Sub-Store-abe6a96944724dc6a36833d5c9ab7c87
|
||||||
|
|
||||||
|
## App 版
|
||||||
|
|
||||||
### 1. Loon
|
### 1. Loon
|
||||||
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
|
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
|
||||||
|
|
||||||
### 2. Surge
|
### 2. Surge
|
||||||
|
|
||||||
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的分类和参数设置): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
#### 关于 Surge 的格外说明
|
||||||
|
|
||||||
1. 官方默认版模块(目前不带 ability 参数, 不保证以后不会改动): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
Surge Mac 版如何支持 SSR, 如何去除 HTTP 传输层以支持 类似 VMess HTTP 节点等 请查看 [链接参数说明](https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E)
|
||||||
|
|
||||||
2. 固定带 ability 参数版本,可能会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
|
定时处理订阅 功能, 避免 App 内拉取超时, 请查看 [定时处理订阅](https://t.me/zhetengsha/1449)
|
||||||
|
|
||||||
3. 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule)
|
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的特性): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
||||||
|
|
||||||
|
1. 官方默认版模块(支持 App 内使用编辑参数): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
||||||
|
|
||||||
|
> 最新版 Surge 已删除 `ability: http-client-policy` 参数, 模块暂不做修改, 对测落地功能无影响
|
||||||
|
|
||||||
|
2. 经典版, 不支持编辑参数, 固定带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
|
||||||
|
|
||||||
|
3. 经典版, 不支持编辑参数, 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule)
|
||||||
|
|
||||||
|
|
||||||
### 3. QX
|
### 3. QX
|
||||||
@@ -31,9 +43,17 @@ Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
|||||||
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
|
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
|
||||||
|
|
||||||
### 5. Shadowrocket
|
### 5. Shadowrocket
|
||||||
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule) 即可。
|
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 即可。
|
||||||
|
|
||||||
## 使用 Sub-Store
|
## 使用 Sub-Store
|
||||||
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
|
||||||
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
|
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
|
||||||
3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。
|
3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。
|
||||||
|
|
||||||
|
## 链接参数说明
|
||||||
|
|
||||||
|
https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
|
||||||
|
|
||||||
|
## 脚本使用说明
|
||||||
|
|
||||||
|
https://github.com/sub-store-org/Sub-Store/wiki/%E8%84%9A%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
name: Sub-Store
|
name: Sub-Store
|
||||||
desc: 高级订阅管理工具 @Peng-YM. 定时任务默认为每天 0 点
|
desc: 高级订阅管理工具 @Peng-YM. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
|
icon: https://raw.githubusercontent.com/cc63/ICON/main/Sub-Store.png
|
||||||
|
|
||||||
http:
|
http:
|
||||||
mitm:
|
mitm:
|
||||||
@@ -19,7 +20,7 @@ http:
|
|||||||
cron:
|
cron:
|
||||||
script:
|
script:
|
||||||
- name: cron-sync-artifacts
|
- name: cron-sync-artifacts
|
||||||
cron: "0 0 * * *"
|
cron: "55 23 * * *"
|
||||||
timeout: 120
|
timeout: 120
|
||||||
|
|
||||||
script-providers:
|
script-providers:
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
#!name=Sub-Store(β)
|
#!name=Sub-Store(β)
|
||||||
#!desc=支持最新 Surge iOS TestFlight 版本的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 0 0 * * *
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
#!arguments=ability:http-client-policy,cronexp:0 0 * * *,sync:"Sub-Store Sync"
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto,produce:"# Sub-Store Produce",produce_cronexp:50 */6 * * *,produce_sub:"sub1,sub2",produce_col:"col1,col2"
|
||||||
#!arguments-desc="\n1️⃣ ability\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n⚠️ Surge 上时候可能会爆内存\n不需要使用的时候应该关闭\n填写任意其他值关闭\n\n2️⃣ cronexp\n同步配置定时任务\n默认为每天 0 点\n\n3️⃣ sync\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务"
|
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n脚本超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6️⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7️⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9️⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠️ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname = %APPEND% sub.store
|
hostname = %APPEND% sub.store
|
||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability="{{{ability}}}"
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}}
|
||||||
|
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
||||||
|
|
||||||
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
||||||
|
|
||||||
|
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 0 点
|
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname = %APPEND% sub.store
|
hostname = %APPEND% sub.store
|
||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 则可以使用此脚本
|
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 则可以使用此脚本
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 可能会爆内存, 如果不需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 0 点
|
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
@@ -7,6 +7,6 @@ hostname = %APPEND% sub.store
|
|||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加国旗脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 0 点
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto,produce:"# Sub-Store Produce",produce_cronexp:50 */6 * * *,produce_sub:"sub1,sub2",produce_col:"col1,col2"
|
||||||
|
#!arguments-desc=\n1️⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2️⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'\n\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n脚本超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6️⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7️⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9️⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠️ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
hostname = %APPEND% sub.store
|
hostname = %APPEND% sub.store
|
||||||
|
|
||||||
[Script]
|
[Script]
|
||||||
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
|
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}}
|
||||||
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
|
||||||
|
|
||||||
Sub-Store Sync=type=cron,cronexp=0 0 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
|
||||||
|
|
||||||
|
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
||||||
|
|
||||||
|
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"
|
||||||
101
scripts/demo.js
101
scripts/demo.js
@@ -7,9 +7,12 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// proxies 为传入的内部节点数组
|
// proxies 为传入的内部节点数组
|
||||||
// 结构大致参考了 Clash.Meta(mihomo) 有私货
|
// 结构大致参考了 Clash.Meta(mihomo) 有私货
|
||||||
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
||||||
// 1. `no-resolve` 为不解析域名
|
// 1. `_no-resolve` 为不解析域名
|
||||||
// 2. 域名解析后 会多一个 `resolved` 字段
|
// 2. 域名解析后 会多一个 `_resolved` 字段
|
||||||
// 3. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_domain` 字段
|
||||||
|
// 4. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
||||||
|
// 5. `_subName` 为单条订阅名
|
||||||
|
// 6. `_collectionName` 为组合订阅名
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@@ -22,6 +25,9 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// scriptResourceCache 缓存
|
// scriptResourceCache 缓存
|
||||||
// 可参考 https://t.me/zhetengsha/1003
|
// 可参考 https://t.me/zhetengsha/1003
|
||||||
|
// const cache = scriptResourceCache
|
||||||
|
// cache.set(id, data)
|
||||||
|
// cache.get(id)
|
||||||
|
|
||||||
// ProxyUtils 为节点处理工具
|
// ProxyUtils 为节点处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/1066
|
// 可参考 https://t.me/zhetengsha/1066
|
||||||
@@ -33,11 +39,96 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// isIPv6,
|
// isIPv6,
|
||||||
// isIP,
|
// isIP,
|
||||||
// yaml, // yaml 解析和生成
|
// yaml, // yaml 解析和生成
|
||||||
|
// getFlag, // 获取 emoji 旗帜
|
||||||
|
// removeFlag, // 移除 emoji 旗帜
|
||||||
|
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||||
|
// Gist, // Gist 类
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 示例: 给节点名添加前缀
|
||||||
|
// $server.name = `[${ProxyUtils.getISO($server.name)}] ${$server.name}`
|
||||||
|
// 示例: 给节点名添加旗帜
|
||||||
|
// $server.name = `[${ProxyUtils.getFlag($server.name).replace(/🇹🇼/g, '🇼🇸')}] ${ProxyUtils.removeFlag($server.name)}`
|
||||||
|
|
||||||
|
// 示例: 从 sni 文件中读取内容并进行节点操作
|
||||||
|
// const sni = await produceArtifact({
|
||||||
|
// type: 'file',
|
||||||
|
// name: 'sni' // 文件名
|
||||||
|
// });
|
||||||
|
// $server.sni = sni
|
||||||
|
|
||||||
|
// 1. Surge 输出 WireGuard 完整配置
|
||||||
|
|
||||||
|
// let proxies = await produceArtifact({
|
||||||
|
// type: 'subscription',
|
||||||
|
// name: 'sub',
|
||||||
|
// platform: 'Surge',
|
||||||
|
// produceOpts: {
|
||||||
|
// 'include-unsupported-proxy': true,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// $content = proxies
|
||||||
|
|
||||||
|
// 2. sing-box
|
||||||
|
|
||||||
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1241
|
||||||
|
|
||||||
|
// let singboxProxies = await produceArtifact({
|
||||||
|
// type: 'subscription', // type: 'subscription' 或 'collection'
|
||||||
|
// name: 'sub', // subscription name
|
||||||
|
// platform: 'sing-box', // target platform
|
||||||
|
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( JSON.parse('JSON String') )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // JSON
|
||||||
|
// $content = JSON.stringify({}, null, 2)
|
||||||
|
|
||||||
|
// 3. clash.meta
|
||||||
|
|
||||||
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1234
|
||||||
|
|
||||||
|
// let clashMetaProxies = await produceArtifact({
|
||||||
|
// type: 'subscription',
|
||||||
|
// name: 'sub',
|
||||||
|
// platform: 'ClashMeta',
|
||||||
|
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( ProxyUtils.yaml.safeLoad('YAML String').proxies )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// 4. 一个比较折腾的方案: 在脚本操作中, 把内容同步到另一个 gist
|
||||||
|
// 见 https://t.me/zhetengsha/1428
|
||||||
|
//
|
||||||
|
// const content = ProxyUtils.produce(proxies, platform)
|
||||||
|
|
||||||
|
// // YAML
|
||||||
|
// ProxyUtils.yaml.load('YAML String')
|
||||||
|
// ProxyUtils.yaml.safeLoad('YAML String')
|
||||||
|
// $content = ProxyUtils.yaml.safeDump({})
|
||||||
|
// $content = ProxyUtils.yaml.dump({})
|
||||||
|
|
||||||
|
// 一个往文件里插入本地节点的例子:
|
||||||
|
// const yaml = ProxyUtils.yaml.safeLoad($content ?? $files[0])
|
||||||
|
// let clashMetaProxies = await produceArtifact({
|
||||||
|
// type: 'collection',
|
||||||
|
// name: '机场',
|
||||||
|
// platform: 'ClashMeta',
|
||||||
|
// produceType: 'internal'
|
||||||
|
// })
|
||||||
|
// yaml.proxies.unshift(...clashMetaProxies)
|
||||||
|
// $content = ProxyUtils.yaml.dump(yaml)
|
||||||
|
|
||||||
|
|
||||||
|
// { $content, $files } will be passed to the next operator
|
||||||
|
// $content is the final content of the file
|
||||||
|
|
||||||
// flowUtils 为机场订阅流量信息处理工具
|
// flowUtils 为机场订阅流量信息处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/948
|
// 可参考:
|
||||||
// https://github.com/sub-store-org/Sub-Store/blob/31b6dd0507a9286d6ab834ec94ad3050f6bdc86b/backend/src/utils/download.js#L104
|
// 1. https://t.me/zhetengsha/948
|
||||||
|
|
||||||
// context 为传入的上下文
|
// context 为传入的上下文
|
||||||
// 有三种情况, 按需判断
|
// 有三种情况, 按需判断
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* 【字体】
|
* 【字体】
|
||||||
* 可参考:https://www.dute.org/weird-fonts
|
* 可参考:https://www.dute.org/weird-fonts
|
||||||
* serif-bold, serif-italic, serif-bold-italic, sans-serif-regular, sans-serif-bold-italic, script-regular, script-bold, fraktur-regular, fraktur-bold, monospace-regular, double-struck-bold, circle-regular, square-regular
|
* serif-bold, serif-italic, serif-bold-italic, sans-serif-regular, sans-serif-bold-italic, script-regular, script-bold, fraktur-regular, fraktur-bold, monospace-regular, double-struck-bold, circle-regular, square-regular, modifier-letter(小写没有 q, 用 ᵠ 替代. 大写缺的太多, 用小写替代)
|
||||||
*
|
*
|
||||||
* 【示例】
|
* 【示例】
|
||||||
* 1️⃣ 设置所有格式为 "serif-bold"
|
* 1️⃣ 设置所有格式为 "serif-bold"
|
||||||
@@ -31,6 +31,7 @@ function operator(proxies) {
|
|||||||
"double-struck-bold": ["𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝔸","𝔹","ℂ","𝔻","𝔼","𝔽","𝔾","ℍ","𝕀","𝕁","𝕂","𝕃","𝕄","ℕ","𝕆","ℙ","ℚ","ℝ","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐","ℤ"],
|
"double-struck-bold": ["𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝔸","𝔹","ℂ","𝔻","𝔼","𝔽","𝔾","ℍ","𝕀","𝕁","𝕂","𝕃","𝕄","ℕ","𝕆","ℙ","ℚ","ℝ","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐","ℤ"],
|
||||||
"circle-regular": ["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ"],
|
"circle-regular": ["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ"],
|
||||||
"square-regular": ["0","1","2","3","4","5","6","7","8","9","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"],
|
"square-regular": ["0","1","2","3","4","5","6","7","8","9","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"],
|
||||||
|
"modifier-letter": ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", "ᵃ", "ᵇ", "ᶜ", "ᵈ", "ᵉ", "ᶠ", "ᵍ", "ʰ", "ⁱ", "ʲ", "ᵏ", "ˡ", "ᵐ", "ⁿ", "ᵒ", "ᵖ", "ᵠ", "ʳ", "ˢ", "ᵗ", "ᵘ", "ᵛ", "ʷ", "ˣ", "ʸ", "ᶻ", "ᴬ", "ᴮ", "ᶜ", "ᴰ", "ᴱ", "ᶠ", "ᴳ", "ʰ", "ᴵ", "ᴶ", "ᴷ", "ᴸ", "ᴹ", "ᴺ", "ᴼ", "ᴾ", "ᵠ", "ᴿ", "ˢ", "ᵀ", "ᵁ", "ᵛ", "ᵂ", "ˣ", "ʸ", "ᶻ"],
|
||||||
};
|
};
|
||||||
|
|
||||||
// charCode => index in `TABLE`
|
// charCode => index in `TABLE`
|
||||||
|
|||||||
79
scripts/ip-flag-node.js
Normal file
79
scripts/ip-flag-node.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const $ = $substore;
|
||||||
|
|
||||||
|
const {onlyFlagIP = true} = $arguments
|
||||||
|
|
||||||
|
async function operator(proxies) {
|
||||||
|
const BATCH_SIZE = 10;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (i < proxies.length) {
|
||||||
|
const batch = proxies.slice(i, i + BATCH_SIZE);
|
||||||
|
await Promise.all(batch.map(async proxy => {
|
||||||
|
if (onlyFlagIP && !ProxyUtils.isIP(proxy.server)) return;
|
||||||
|
try {
|
||||||
|
// remove the original flag
|
||||||
|
let proxyName = removeFlag(proxy.name);
|
||||||
|
|
||||||
|
// query ip-api
|
||||||
|
const countryCode = await queryIpApi(proxy);
|
||||||
|
|
||||||
|
proxyName = getFlagEmoji(countryCode) + ' ' + proxyName;
|
||||||
|
proxy.name = proxyName;
|
||||||
|
} catch (err) {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
await sleep(1000);
|
||||||
|
i += BATCH_SIZE;
|
||||||
|
}
|
||||||
|
return proxies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function queryIpApi(proxy) {
|
||||||
|
const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:78.0) Gecko/20100101 Firefox/78.0";
|
||||||
|
const headers = {
|
||||||
|
"User-Agent": ua
|
||||||
|
};
|
||||||
|
const result = new Promise((resolve, reject) => {
|
||||||
|
const url =
|
||||||
|
`http://ip-api.com/json/${encodeURIComponent(proxy.server)}?lang=zh-CN`;
|
||||||
|
$.http.get({
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
}).then(resp => {
|
||||||
|
const data = JSON.parse(resp.body);
|
||||||
|
if (data.status === "success") {
|
||||||
|
resolve(data.countryCode);
|
||||||
|
} else {
|
||||||
|
reject(new Error(data.message));
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlagEmoji(countryCode) {
|
||||||
|
const codePoints = countryCode
|
||||||
|
.toUpperCase()
|
||||||
|
.split('')
|
||||||
|
.map(char => 127397 + char.charCodeAt());
|
||||||
|
return String
|
||||||
|
.fromCodePoint(...codePoints)
|
||||||
|
.replace(/🇹🇼/g, '🇨🇳');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFlag(str) {
|
||||||
|
return str
|
||||||
|
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
1
web
1
web
Submodule web deleted from b10b708c34
Reference in New Issue
Block a user