mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2026-03-16 14:42:36 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c29771de9 | ||
|
|
5845ea1a66 | ||
|
|
eb32a09def | ||
|
|
c60aec603f | ||
|
|
0072739f01 | ||
|
|
1bd2f5f643 | ||
|
|
0a31f5d5d7 | ||
|
|
6484edb5db | ||
|
|
c073870f24 | ||
|
|
e93332048e | ||
|
|
4dcb9ae79e | ||
|
|
6ea3575101 | ||
|
|
26820ea892 | ||
|
|
f64e8ecfe4 | ||
|
|
77604a3544 | ||
|
|
8f5d027080 | ||
|
|
5244de4dba | ||
|
|
4121ec2970 | ||
|
|
a949c49192 | ||
|
|
9fba3506f0 | ||
|
|
9677c7ebbd | ||
|
|
03149dcadb | ||
|
|
1bfa6ebb2c | ||
|
|
4cd525824e | ||
|
|
813f2b839d | ||
|
|
3d58534dfe | ||
|
|
f7333c0617 | ||
|
|
f7d4b66db6 | ||
|
|
8c844eb23a | ||
|
|
de892aaa2b | ||
|
|
b143476e71 | ||
|
|
2c4e47166d | ||
|
|
6881148021 | ||
|
|
848491c0f8 | ||
|
|
49c8f2e521 |
46
.github/workflows/main.yml
vendored
46
.github/workflows/main.yml
vendored
@@ -1,13 +1,15 @@
|
||||
name: update
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- 'backend/package.json'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- 'backend/package.json'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -21,16 +23,30 @@ jobs:
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Install dependencies
|
||||
run: cd backend && npm i
|
||||
- name: Test
|
||||
run: cd backend && npm test
|
||||
- name: Build
|
||||
run: cd backend && npm run build
|
||||
- name: Commit changes
|
||||
if: contains(github.event.head_commit.message, '#build')
|
||||
run: |
|
||||
git config user.email github-actions
|
||||
git config user.name github-actions@github.com
|
||||
git add .
|
||||
git commit -m "Build sub-store.min.js" -a
|
||||
git push
|
||||
npm install -g pnpm
|
||||
cd backend && pnpm i
|
||||
- name: Test
|
||||
run: |
|
||||
cd backend
|
||||
pnpm test
|
||||
- name: Build
|
||||
run: |
|
||||
cd backend
|
||||
pnpm run build
|
||||
- id: tag
|
||||
name: Generate release tag
|
||||
run: |
|
||||
cd backend
|
||||
SUBSTORE_RELEASE=`node --eval="process.stdout.write(require('./package.json').version)"`
|
||||
echo "::set-output name=release_tag::$SUBSTORE_RELEASE"
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.release_tag }}
|
||||
files: |
|
||||
./backend/sub-store.min.js
|
||||
./backend/dist/cron-sync-artifacts.min.js
|
||||
./backend/dist/sub-store-parser.loon.min.js
|
||||
|
||||
6
backend/dist/cron-sync-artifacts.min.js
vendored
6
backend/dist/cron-sync-artifacts.min.js
vendored
File diff suppressed because one or more lines are too long
6
backend/dist/sub-store-parser.loon.min.js
vendored
6
backend/dist/sub-store-parser.loon.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.8.6",
|
||||
"version": "2.12.9",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -19,6 +19,7 @@
|
||||
"js-base64": "^3.7.2",
|
||||
"lodash": "^4.17.21",
|
||||
"request": "^2.88.2",
|
||||
"requests": "^0.3.0",
|
||||
"semver": "^7.3.7",
|
||||
"static-js-yaml": "^1.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
87
backend/pnpm-lock.yaml
generated
87
backend/pnpm-lock.yaml
generated
@@ -31,6 +31,7 @@ specifiers:
|
||||
prettier: 2.6.2
|
||||
prettier-plugin-sort-imports: ^1.6.1
|
||||
request: ^2.88.2
|
||||
requests: ^0.3.0
|
||||
semver: ^7.3.7
|
||||
static-js-yaml: ^1.0.0
|
||||
tinyify: ^3.0.0
|
||||
@@ -43,6 +44,7 @@ dependencies:
|
||||
js-base64: registry.npmmirror.com/js-base64/3.7.2
|
||||
lodash: registry.npmmirror.com/lodash/4.17.21
|
||||
request: registry.npmmirror.com/request/2.88.2
|
||||
requests: registry.npmmirror.com/requests/0.3.0
|
||||
semver: registry.npmmirror.com/semver/7.3.7
|
||||
static-js-yaml: registry.npmmirror.com/static-js-yaml/1.0.0
|
||||
uuid: registry.npmmirror.com/uuid/8.3.2
|
||||
@@ -2250,6 +2252,12 @@ packages:
|
||||
follow-redirects: registry.npmmirror.com/follow-redirects/1.13.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/axo/0.0.2:
|
||||
resolution: {integrity: sha512-8CC4Mb+OhK97UEng0PgiqUDNZjzVcWDsV+G2vLYCQn1jEL7y6VqiRVlZlRu+aA/ckSznmNzW6X1I6nj2As/haQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/axo/-/axo-0.0.2.tgz}
|
||||
name: axo
|
||||
version: 0.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3:
|
||||
resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz}
|
||||
name: babel-plugin-dynamic-import-node
|
||||
@@ -4296,6 +4304,12 @@ packages:
|
||||
es5-ext: registry.npmmirror.com/es5-ext/0.10.61
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/eventemitter3/4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz}
|
||||
name: eventemitter3
|
||||
version: 4.0.7
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/events/3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/events/-/events-3.3.0.tgz}
|
||||
name: events
|
||||
@@ -4410,6 +4424,12 @@ packages:
|
||||
name: extend
|
||||
version: 3.0.2
|
||||
|
||||
registry.npmmirror.com/extendible/0.1.1:
|
||||
resolution: {integrity: sha512-AglckQA0TJV8/ZmhQcNmaaFcFFPXFIoZbfuoQOlGDK7Jh/roWotYzJ7ik1FBBCHBr8n7CgTR8lXXPAN8Rfb7rw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extendible/-/extendible-0.1.1.tgz}
|
||||
name: extendible
|
||||
version: 0.1.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/extglob/2.0.4:
|
||||
resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz}
|
||||
name: extglob
|
||||
@@ -4435,6 +4455,12 @@ packages:
|
||||
engines: {'0': node >=0.6.0}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/failure/1.1.1:
|
||||
resolution: {integrity: sha512-lzrrk0NUfjVeU3jLmfU01zP5bfg4XVFxHREYGvgJowaCqHLSQtqIGENH/CU+oSs6yfYObdSM7b9UY/3p2VJOSg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/failure/-/failure-1.1.1.tgz}
|
||||
name: failure
|
||||
version: 1.1.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/falafel/2.2.5:
|
||||
resolution: {integrity: sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/falafel/-/falafel-2.2.5.tgz}
|
||||
name: falafel
|
||||
@@ -5219,6 +5245,12 @@ packages:
|
||||
glogg: registry.npmmirror.com/glogg/1.0.2
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/hang/1.0.0:
|
||||
resolution: {integrity: sha512-vtBz98Bt/Tbm03cZO5Ymc7ZL8ead/jIx9T5Wg/xuz+9BXPAJNJSdGQW63LoaesogUQKTpHyal339hxTaTf/APg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hang/-/hang-1.0.0.tgz}
|
||||
name: hang
|
||||
version: 1.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/har-schema/2.0.0:
|
||||
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz}
|
||||
name: har-schema
|
||||
@@ -6336,6 +6368,17 @@ packages:
|
||||
strip-bom: registry.npmmirror.com/strip-bom/2.0.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/loads/0.0.4:
|
||||
resolution: {integrity: sha512-XjPzzYIHkuMNqYyvh6AECQAHi682nyKO9TMdMYnaz7QbPDI/KIeSIjRhAlXIbRMPYAgtLUYgPlD3mtKZ4Q8SYA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/loads/-/loads-0.0.4.tgz}
|
||||
name: loads
|
||||
version: 0.0.4
|
||||
dependencies:
|
||||
failure: registry.npmmirror.com/failure/1.1.1
|
||||
one-time: registry.npmmirror.com/one-time/0.0.4
|
||||
xhr-response: registry.npmmirror.com/xhr-response/1.0.1
|
||||
xhr-status: registry.npmmirror.com/xhr-status/1.0.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/locate-path/3.0.0:
|
||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz}
|
||||
name: locate-path
|
||||
@@ -6876,6 +6919,12 @@ packages:
|
||||
semver: registry.npmmirror.com/semver/5.7.1
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/node-http-xhr/1.3.4:
|
||||
resolution: {integrity: sha512-0bA08/2RKWxw6pMkOVd3KP+0F5+ifQLMMTDxrCgxlgkoU1N8DhCbCSAYEqpgaVYM2smvbVVewiXjW+8AyoLfxQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-http-xhr/-/node-http-xhr-1.3.4.tgz}
|
||||
name: node-http-xhr
|
||||
version: 1.3.4
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/node-releases/2.0.4:
|
||||
resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.4.tgz}
|
||||
name: node-releases
|
||||
@@ -7110,6 +7159,12 @@ packages:
|
||||
wrappy: registry.npmmirror.com/wrappy/1.0.2
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/one-time/0.0.4:
|
||||
resolution: {integrity: sha512-qAMrwuk2xLEutlASoiPiAMW3EN3K96Ka/ilSXYr6qR1zSVXw2j7+yDSqGTC4T9apfLYxM3tLLjKvgPdAUK7kYQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/one-time/-/one-time-0.0.4.tgz}
|
||||
name: one-time
|
||||
version: 0.0.4
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/optionator/0.8.3:
|
||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz}
|
||||
name: optionator
|
||||
@@ -8057,6 +8112,20 @@ packages:
|
||||
uuid: registry.npmmirror.com/uuid/3.4.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/requests/0.3.0:
|
||||
resolution: {integrity: sha512-1B6nkiHjC1O1cSgFhEwkc+xd8vuj04h7xSmCg5yI8nmhCIKbPkX47od8erQ2pokBt5qxUO7dwP4jplXD6k6ISA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/requests/-/requests-0.3.0.tgz}
|
||||
name: requests
|
||||
version: 0.3.0
|
||||
dependencies:
|
||||
axo: registry.npmmirror.com/axo/0.0.2
|
||||
eventemitter3: registry.npmmirror.com/eventemitter3/4.0.7
|
||||
extendible: registry.npmmirror.com/extendible/0.1.1
|
||||
hang: registry.npmmirror.com/hang/1.0.0
|
||||
loads: registry.npmmirror.com/loads/0.0.4
|
||||
node-http-xhr: registry.npmmirror.com/node-http-xhr/1.3.4
|
||||
xhr-send: registry.npmmirror.com/xhr-send/1.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/require-directory/2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz}
|
||||
name: require-directory
|
||||
@@ -9731,6 +9800,24 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/xhr-response/1.0.1:
|
||||
resolution: {integrity: sha512-m2FlVRCl3VqDcpc8UaWZJpwuHpFR2vYeXv6ipXU2Uuu4vNKFYVEFI0emuJN370Fge+JCbiAnS+JJmSoWVmWrjQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-response/-/xhr-response-1.0.1.tgz}
|
||||
name: xhr-response
|
||||
version: 1.0.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xhr-send/1.0.0:
|
||||
resolution: {integrity: sha512-789EG4qW6Z0nPvG74AV3WWQCnBG5HxJXNiBsnEivZ8OpbvVA0amH0+g+MNT99o5kt/XLdRezm5KS1wJzcGJztw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-send/-/xhr-send-1.0.0.tgz}
|
||||
name: xhr-send
|
||||
version: 1.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xhr-status/1.0.1:
|
||||
resolution: {integrity: sha512-VF0WSqtmkf56OmF26LCWsWvRb1a+WYGdHDoQnPPCVUQTM8CVUAOBcUDsm7nP7SQcgEEdrvF4DmhEADuXdGieyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-status/-/xhr-status-1.0.1.tgz}
|
||||
name: xhr-status
|
||||
version: 1.0.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xtend/2.1.2:
|
||||
resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xtend/-/xtend-2.1.2.tgz}
|
||||
name: xtend
|
||||
|
||||
@@ -7,3 +7,5 @@ export const RULES_KEY = 'rules';
|
||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
||||
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
||||
export const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
|
||||
export const CACHE_EXPIRATION_TIME_MS = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
@@ -50,6 +50,7 @@ function parse(raw) {
|
||||
lastParser = parser;
|
||||
success = true;
|
||||
$.info(`${parser.name} is activated`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +85,7 @@ async function process(proxies, operators = [], targetPlatform) {
|
||||
// if this is a remote script, download it
|
||||
try {
|
||||
script = await download(url.split('#')[0]);
|
||||
$.info(`Script loaded: >>>\n ${script}`);
|
||||
// $.info(`Script loaded: >>>\n ${script}`);
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`,
|
||||
|
||||
@@ -2,6 +2,8 @@ import { getIfNotBlank, isPresent, isNotBlank, getIfPresent } from '@/utils';
|
||||
import getSurgeParser from './peggy/surge';
|
||||
import getLoonParser from './peggy/loon';
|
||||
import getQXParser from './peggy/qx';
|
||||
import getTrojanURIParser from './peggy/trojan-uri';
|
||||
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||
@@ -112,8 +114,11 @@ function URI_SSR() {
|
||||
line = line.split('/?')[1].split('&');
|
||||
if (line.length > 1) {
|
||||
for (const item of line) {
|
||||
const [key, val] = item.split('=');
|
||||
other_params[key] = val.trim();
|
||||
let [key, val] = item.split('=');
|
||||
val = val.trim();
|
||||
if (val.length > 0) {
|
||||
other_params[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy = {
|
||||
@@ -237,36 +242,9 @@ function URI_Trojan() {
|
||||
};
|
||||
|
||||
const parse = (line) => {
|
||||
line = line.split('trojan://')[1];
|
||||
const [server, port] = line.split('@')[1].split('?')[0].split(':');
|
||||
const name = decodeURIComponent(line.split('#')[1].trim());
|
||||
let paramArr = line.split('?');
|
||||
let scert = null;
|
||||
let params;
|
||||
if (paramArr.length > 1) {
|
||||
paramArr = paramArr[1].split('#')[0].split('&');
|
||||
params = new Map(
|
||||
paramArr.map((item) => {
|
||||
return item.split('=');
|
||||
}),
|
||||
);
|
||||
if (
|
||||
params.get('allowInsecure') === '1' ||
|
||||
params.get('allowInsecure') === 'true'
|
||||
) {
|
||||
scert = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: name || `[Trojan] ${server}`, // trojan uri may have no server tag!
|
||||
type: 'trojan',
|
||||
server,
|
||||
port,
|
||||
password: line.split('@')[0],
|
||||
sni: getIfPresent(params.get('sni')),
|
||||
'skip-cert-verify': getIfPresent(scert),
|
||||
};
|
||||
const parser = getTrojanURIParser();
|
||||
const proxy = parser.parse(line);
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
@@ -281,7 +259,32 @@ function Clash_All() {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const parse = (line) => JSON.parse(line);
|
||||
const parse = (line) => {
|
||||
const proxy = JSON.parse(line);
|
||||
if (
|
||||
![
|
||||
'ss',
|
||||
'ssr',
|
||||
'vmess',
|
||||
'socks',
|
||||
'http',
|
||||
'snell',
|
||||
'trojan',
|
||||
].includes(proxy.type)
|
||||
) {
|
||||
throw new Error(
|
||||
`Clash does not support proxy with type: ${proxy.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
// handle vmess sni
|
||||
if (proxy.type === 'vmess') {
|
||||
proxy.sni = proxy.servername;
|
||||
delete proxy.servername;
|
||||
}
|
||||
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_ss/obfs_ho
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
@@ -105,17 +105,16 @@ domain = match:[0-9a-zA-z-_.]+ {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
throw new Error("Invalid port number: " + port);
|
||||
}
|
||||
|
||||
method = comma cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"auto");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
|
||||
@@ -54,7 +54,7 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_ss/obfs_ho
|
||||
}
|
||||
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/vmess_alterId/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
proxy.alterId = proxy.alterId || 0;
|
||||
handleTransport();
|
||||
}
|
||||
@@ -103,17 +103,16 @@ domain = match:[0-9a-zA-z-_.]+ {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
throw new Error("Invalid port number: " + port);
|
||||
}
|
||||
|
||||
method = comma cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"auto");
|
||||
|
||||
username = & {
|
||||
let j = peg$currPos;
|
||||
|
||||
@@ -82,7 +82,7 @@ shadowsocks = "shadowsocks" equals address
|
||||
vmess = "vmess" equals address
|
||||
(uuid/method/over_tls/tls_host/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
@@ -130,7 +130,7 @@ ip = & {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,9 @@ password = comma "password" equals password:[^=,]+ { proxy.password = password.j
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
proxy.cipher = cipher;
|
||||
};
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
|
||||
@@ -80,7 +80,7 @@ shadowsocks = "shadowsocks" equals address
|
||||
vmess = "vmess" equals address
|
||||
(uuid/method/over_tls/tls_host/tls_fingerprint/tls_verification/tag/obfs/obfs_host/obfs_uri/udp_relay/udp_over_tcp/fast_open/aead/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
@@ -128,7 +128,7 @@ ip = & {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -138,10 +138,9 @@ password = comma "password" equals password:[^=,]+ { proxy.password = password.j
|
||||
uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim(); }
|
||||
|
||||
method = comma "method" equals cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
proxy.cipher = cipher;
|
||||
};
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
|
||||
@@ -45,7 +45,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
@@ -109,7 +109,7 @@ domain = match:[0-9a-zA-z-_.]+ {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -152,10 +152,9 @@ vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join("");
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
method = comma "encrypt-method" equals cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||
|
||||
@@ -43,7 +43,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
|
||||
}
|
||||
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/method/tls/sni/tls_fingerprint/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "vmess";
|
||||
proxy.cipher = proxy.cipher || "auto";
|
||||
proxy.cipher = proxy.cipher || "none";
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
} else {
|
||||
@@ -107,7 +107,7 @@ domain = match:[0-9a-zA-z-_.]+ {
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 80 && port <= 65535) {
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -150,10 +150,9 @@ vmess_uuid = comma "username" equals match:[^,]+ { proxy.uuid = match.join("");
|
||||
vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
|
||||
|
||||
method = comma "encrypt-method" equals cipher:cipher {
|
||||
if (cipher !== 'none') proxy.cipher = cipher;
|
||||
else proxy.cipher = 'auto';
|
||||
proxy.cipher = cipher;
|
||||
}
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
cipher = ("aes-128-gcm"/"aes-192-gcm"/"aes-256-gcm"/"aes-128-cfb"/"aes-192-cfb"/"aes-256-cfb"/"aes-128-ctr"/"aes-192-ctr"/"aes-256-ctr"/"rc4-md5"/"xchacha20-ietf-poly1305"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none");
|
||||
|
||||
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
|
||||
ws_headers = comma "ws-headers" equals headers:$[^,]+ {
|
||||
|
||||
115
backend/src/core/proxy-utils/parsers/peggy/trojan-uri.js
Normal file
115
backend/src/core/proxy-utils/parsers/peggy/trojan-uri.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as peggy from 'peggy';
|
||||
const grammars = String.raw`
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
|
||||
function toBool(str) {
|
||||
if (typeof str === 'undefined' || str === null) return undefined;
|
||||
return /(TRUE)|1/i.test(str);
|
||||
}
|
||||
}}
|
||||
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
const params = {};
|
||||
}
|
||||
|
||||
start = (trojan) {
|
||||
return proxy
|
||||
}
|
||||
|
||||
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
|
||||
proxy.type = "trojan";
|
||||
proxy.password = password;
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
proxy.name = name;
|
||||
|
||||
// name may be empty
|
||||
if (!proxy.name) {
|
||||
proxy.name = server + ":" + port;
|
||||
}
|
||||
};
|
||||
|
||||
password = match:$[^@]+ {
|
||||
return decodeURIComponent(match);
|
||||
};
|
||||
|
||||
server = ip/domain;
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let end;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
if (input[j] === ":") end = j;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = end || j;
|
||||
$.ip = input.substring(start, end).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
} else {
|
||||
throw new Error("Invalid port: " + port);
|
||||
}
|
||||
}
|
||||
|
||||
params = "?" head:param tail:("&"@param)* {
|
||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||
proxy.sni = params["sni"] || params["peer"];
|
||||
|
||||
if (toBool(params["ws"])) {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", params["wspath"]);
|
||||
}
|
||||
|
||||
proxy.udp = toBool(params["udp"]);
|
||||
proxy.tfo = toBool(params["tfo"]);
|
||||
}
|
||||
|
||||
param = kv/single;
|
||||
|
||||
kv = key:$[a-z]i+ "=" value:$[^&#]i+ {
|
||||
params[key] = value;
|
||||
}
|
||||
|
||||
single = key:$[a-z]i+ {
|
||||
params[key] = true;
|
||||
};
|
||||
|
||||
name = "#" + match:$.* {
|
||||
return decodeURIComponent(match);
|
||||
}
|
||||
`;
|
||||
let parser;
|
||||
export default function getParser() {
|
||||
if (!parser) {
|
||||
parser = peggy.generate(grammars);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
105
backend/src/core/proxy-utils/parsers/peggy/trojan-uri.peg
Normal file
105
backend/src/core/proxy-utils/parsers/peggy/trojan-uri.peg
Normal file
@@ -0,0 +1,105 @@
|
||||
// global initializer
|
||||
{{
|
||||
function $set(obj, path, value) {
|
||||
if (Object(obj) !== obj) return obj;
|
||||
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
|
||||
path
|
||||
.slice(0, -1)
|
||||
.reduce((a, c, i) => (Object(a[c]) === a[c] ? a[c] : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {})), obj)[
|
||||
path[path.length - 1]
|
||||
] = value;
|
||||
return obj;
|
||||
}
|
||||
|
||||
function toBool(str) {
|
||||
if (typeof str === 'undefined' || str === null) return undefined;
|
||||
return /(TRUE)|1/i.test(str);
|
||||
}
|
||||
}}
|
||||
|
||||
{
|
||||
const proxy = {};
|
||||
const obfs = {};
|
||||
const $ = {};
|
||||
const params = {};
|
||||
}
|
||||
|
||||
start = (trojan) {
|
||||
return proxy
|
||||
}
|
||||
|
||||
trojan = "trojan://" password:password "@" server:server ":" port:port params? name:name?{
|
||||
proxy.type = "trojan";
|
||||
proxy.password = password;
|
||||
proxy.server = server;
|
||||
proxy.port = port;
|
||||
proxy.name = name;
|
||||
|
||||
// name may be empty
|
||||
if (!proxy.name) {
|
||||
proxy.name = server + ":" + port;
|
||||
}
|
||||
};
|
||||
|
||||
password = match:$[^@]+ {
|
||||
return decodeURIComponent(match);
|
||||
};
|
||||
|
||||
server = ip/domain;
|
||||
|
||||
domain = match:[0-9a-zA-z-_.]+ {
|
||||
const domain = match.join("");
|
||||
if (/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(domain)) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
ip = & {
|
||||
const start = peg$currPos;
|
||||
let end;
|
||||
let j = start;
|
||||
while (j < input.length) {
|
||||
if (input[j] === ",") break;
|
||||
if (input[j] === ":") end = j;
|
||||
j++;
|
||||
}
|
||||
peg$currPos = end || j;
|
||||
$.ip = input.substring(start, end).trim();
|
||||
return true;
|
||||
} { return $.ip; }
|
||||
|
||||
port = digits:[0-9]+ {
|
||||
const port = parseInt(digits.join(""), 10);
|
||||
if (port >= 0 && port <= 65535) {
|
||||
return port;
|
||||
} else {
|
||||
throw new Error("Invalid port: " + port);
|
||||
}
|
||||
}
|
||||
|
||||
params = "?" head:param tail:("&"@param)* {
|
||||
proxy["skip-cert-verify"] = toBool(params["allowInsecure"]);
|
||||
proxy.sni = params["sni"] || params["peer"];
|
||||
|
||||
if (toBool(params["ws"])) {
|
||||
proxy.network = "ws";
|
||||
$set(proxy, "ws-opts.path", params["wspath"]);
|
||||
}
|
||||
|
||||
proxy.udp = toBool(params["udp"]);
|
||||
proxy.tfo = toBool(params["tfo"]);
|
||||
}
|
||||
|
||||
param = kv/single;
|
||||
|
||||
kv = key:$[a-z]i+ "=" value:$[^&#]i+ {
|
||||
params[key] = value;
|
||||
}
|
||||
|
||||
single = key:$[a-z]i+ {
|
||||
params[key] = true;
|
||||
};
|
||||
|
||||
name = "#" + match:$.* {
|
||||
return decodeURIComponent(match);
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import resourceCache from '@/utils/resource-cache';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
import { FULL } from '@/utils/logical';
|
||||
import { getFlag } from '@/utils/geo';
|
||||
import lodash from 'lodash';
|
||||
import $ from '@/core/app';
|
||||
import { hex_md5 } from '@/vendor/md5';
|
||||
import { ProxyUtils } from '@/core/proxy-utils';
|
||||
|
||||
/**
|
||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||
{
|
||||
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
|
||||
{
|
||||
operator: "AND",
|
||||
child: [
|
||||
{
|
||||
@@ -21,7 +24,7 @@ The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed a
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
||||
function ConditionalFilter({ rule }) {
|
||||
return {
|
||||
@@ -310,6 +313,9 @@ function ScriptOperator(script, targetPlatform, $arguments) {
|
||||
|
||||
const DOMAIN_RESOLVERS = {
|
||||
Google: async function (domain) {
|
||||
const id = hex_md5(`GOOGLE:${domain}`);
|
||||
const cached = resourceCache.get(id);
|
||||
if (cached) return cached;
|
||||
const resp = await $.http.get({
|
||||
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
|
||||
domain,
|
||||
@@ -326,9 +332,14 @@ const DOMAIN_RESOLVERS = {
|
||||
if (answers.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
return answers[answers.length - 1].data;
|
||||
const result = answers[answers.length - 1].data;
|
||||
resourceCache.set(id, result);
|
||||
return result;
|
||||
},
|
||||
'IP-API': async function (domain) {
|
||||
const id = hex_md5(`IP-API:${domain}`);
|
||||
const cached = resourceCache.get(id);
|
||||
if (cached) return cached;
|
||||
const resp = await $.http.get({
|
||||
url: `http://ip-api.com/json/${encodeURIComponent(
|
||||
domain,
|
||||
@@ -338,9 +349,14 @@ const DOMAIN_RESOLVERS = {
|
||||
if (body['status'] !== 'success') {
|
||||
throw new Error(`Status is ${body['status']}`);
|
||||
}
|
||||
return body.query;
|
||||
const result = body.query;
|
||||
resourceCache.set(id, result);
|
||||
return result;
|
||||
},
|
||||
Cloudflare: async function (domain) {
|
||||
const id = hex_md5(`CLOUDFLARE:${domain}`);
|
||||
const cached = resourceCache.get(id);
|
||||
if (cached) return cached;
|
||||
const resp = await $.http.get({
|
||||
url: `https://1.0.0.1/dns-query?name=${encodeURIComponent(
|
||||
domain,
|
||||
@@ -357,7 +373,9 @@ const DOMAIN_RESOLVERS = {
|
||||
if (answers.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
return answers[answers.length - 1].data;
|
||||
const result = answers[answers.length - 1].data;
|
||||
resourceCache.set(id, result);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -614,6 +632,7 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
'$persistentStore',
|
||||
'$httpClient',
|
||||
'$notification',
|
||||
'ProxyUtils',
|
||||
`${script}\n return ${name}`,
|
||||
)(
|
||||
$arguments,
|
||||
@@ -625,13 +644,15 @@ function createDynamicFunction(name, script, $arguments) {
|
||||
$httpClient,
|
||||
// eslint-disable-next-line no-undef
|
||||
$notification,
|
||||
ProxyUtils,
|
||||
);
|
||||
} else {
|
||||
return new Function(
|
||||
'$arguments',
|
||||
'$substore',
|
||||
'lodash',
|
||||
'ProxyUtils',
|
||||
`${script}\n return ${name}`,
|
||||
)($arguments, $, lodash);
|
||||
)($arguments, $, lodash, ProxyUtils);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
|
||||
export default function Clash_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies) => {
|
||||
proxies.filter((proxy) => {
|
||||
if (proxy.type === 'vless') return false;
|
||||
return true;
|
||||
});
|
||||
// filter unsupported proxies
|
||||
proxies = proxies.filter((proxy) =>
|
||||
['ss', 'ssr', 'vmess', 'socks', 'http', 'snell', 'trojan'].includes(
|
||||
proxy.type,
|
||||
),
|
||||
);
|
||||
return (
|
||||
'proxies:\n' +
|
||||
proxies
|
||||
.map((proxy) => {
|
||||
if (proxy.type === 'vmess') {
|
||||
// handle vmess aead
|
||||
if (isPresent(proxy, 'aead')) {
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
}
|
||||
delete proxy.aead;
|
||||
}
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = proxy.sni;
|
||||
delete proxy.sni;
|
||||
}
|
||||
}
|
||||
|
||||
delete proxy['tls-fingerprint'];
|
||||
delete proxy['aead'];
|
||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||
})
|
||||
.join('')
|
||||
|
||||
@@ -72,7 +72,7 @@ function shadowsocksr(proxy) {
|
||||
|
||||
// obfs
|
||||
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
|
||||
result.appendIfPresent(`,obfs-host=${proxy['obfs-param']}`, 'obfs-param');
|
||||
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
|
||||
|
||||
// tfo
|
||||
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPresent, Result } from './utils';
|
||||
|
||||
const targetPlatform = 'QX';
|
||||
|
||||
export default function QX_Producer() {
|
||||
@@ -180,7 +181,16 @@ function vmess(proxy) {
|
||||
const appendIfPresent = result.appendIfPresent.bind(result);
|
||||
|
||||
append(`vmess=${proxy.server}:${proxy.port}`);
|
||||
append(`,method=${proxy.cipher === 'auto' ? 'none' : proxy.cipher}`);
|
||||
|
||||
// cipher
|
||||
let cipher;
|
||||
if (proxy.cipher === 'auto') {
|
||||
cipher = 'chacha20-ietf-poly1305';
|
||||
} else {
|
||||
cipher = proxy.cipher;
|
||||
}
|
||||
append(`,method=${cipher}`);
|
||||
|
||||
append(`,password=${proxy.uuid}`);
|
||||
|
||||
// obfs
|
||||
@@ -196,7 +206,10 @@ function vmess(proxy) {
|
||||
} else {
|
||||
throw new Error(`network ${proxy.network} is unsupported`);
|
||||
}
|
||||
appendIfPresent(`,obfs-uri=${proxy['ws-opts'].path}`, 'ws-opts.path');
|
||||
appendIfPresent(
|
||||
`,obfs-uri=${proxy[`${proxy.network}-opts`].path}`,
|
||||
`${proxy.network}-opts.path`,
|
||||
);
|
||||
appendIfPresent(
|
||||
`,obfs-host=${proxy[`${proxy.network}-opts`].headers.Host}`,
|
||||
`${proxy.network}-opts.headers.Host`,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isPresent } from '@/core/proxy-utils/producers/utils';
|
||||
|
||||
export default function Stash_Producer() {
|
||||
const type = 'ALL';
|
||||
const produce = (proxies) => {
|
||||
@@ -5,8 +7,21 @@ export default function Stash_Producer() {
|
||||
'proxies:\n' +
|
||||
proxies
|
||||
.map((proxy) => {
|
||||
if (proxy.type === 'vmess') {
|
||||
// handle vmess aead
|
||||
if (isPresent(proxy, 'aead')) {
|
||||
if (proxy.aead) {
|
||||
proxy.alterId = 0;
|
||||
}
|
||||
delete proxy.aead;
|
||||
}
|
||||
if (isPresent(proxy, 'sni')) {
|
||||
proxy.servername = proxy.sni;
|
||||
delete proxy.sni;
|
||||
}
|
||||
}
|
||||
|
||||
delete proxy['tls-fingerprint'];
|
||||
delete proxy['aead'];
|
||||
return ' - ' + JSON.stringify(proxy) + '\n';
|
||||
})
|
||||
.join('')
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import { syncToGist, produceArtifact } from '@/restful/artifacts';
|
||||
import { version } from '../../package.json';
|
||||
import { ARTIFACTS_KEY } from '@/constants';
|
||||
import { SETTINGS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||
import $ from '@/core/app';
|
||||
|
||||
console.log(
|
||||
`
|
||||
!(async function () {
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
// if GitHub token is not configured
|
||||
if (!settings.githubUser || !settings.gistToken) return;
|
||||
|
||||
const artifacts = $.read(ARTIFACTS_KEY);
|
||||
if (!artifacts || artifacts.length === 0) return;
|
||||
|
||||
const shouldSync = artifacts.some((artifact) => artifact.sync);
|
||||
if (shouldSync) await doSync();
|
||||
})().finally(() => $.done());
|
||||
|
||||
async function doSync() {
|
||||
console.log(
|
||||
`
|
||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||
Sub-Store -- v${version}
|
||||
Sub-Store Sync -- v${version}
|
||||
┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅
|
||||
`,
|
||||
);
|
||||
!(async function () {
|
||||
);
|
||||
|
||||
$.info('开始同步所有远程配置...');
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||
const files = {};
|
||||
@@ -46,9 +59,9 @@ console.log(
|
||||
}
|
||||
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
$.notify('🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』', '全部订阅同步成功!');
|
||||
$.notify('🌍 Sub-Store', '全部订阅同步成功!');
|
||||
} catch (err) {
|
||||
$.notify('🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』', '同步订阅失败', `原因:${err}`);
|
||||
$.notify('🌍 Sub-Store', '同步订阅失败', `原因:${err}`);
|
||||
$.error(`无法同步订阅配置到 Gist,原因:${err}`);
|
||||
}
|
||||
})().finally(() => $.done());
|
||||
}
|
||||
|
||||
@@ -282,6 +282,9 @@ async function syncToGist(files) {
|
||||
async function produceArtifact({ type, name, platform }) {
|
||||
platform = platform || 'JSON';
|
||||
|
||||
// produce Clash node format for ShadowRocket
|
||||
if (platform === 'ShadowRocket') platform = 'Clash';
|
||||
|
||||
if (type === 'subscription') {
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const sub = findByName(allSubs, name);
|
||||
@@ -304,7 +307,7 @@ async function produceArtifact({ type, name, platform }) {
|
||||
for (const proxy of proxies) {
|
||||
if (exist[proxy.name]) {
|
||||
$.notify(
|
||||
'🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』',
|
||||
'🌍 Sub-Store',
|
||||
'⚠️ 订阅包含重复节点!',
|
||||
'请仔细检测配置!',
|
||||
{
|
||||
@@ -385,7 +388,7 @@ async function produceArtifact({ type, name, platform }) {
|
||||
for (const proxy of proxies) {
|
||||
if (exist[proxy.name]) {
|
||||
$.notify(
|
||||
'🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』',
|
||||
'🌍 Sub-Store',
|
||||
'⚠️ 订阅包含重复节点!',
|
||||
'请仔细检测配置!',
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||
import { COLLECTIONS_KEY } from '@/constants';
|
||||
import { COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||
import { failed, success } from '@/restful/response';
|
||||
import $ from '@/core/app';
|
||||
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
|
||||
@@ -67,6 +67,21 @@ function updateCollection(req, res) {
|
||||
...collection,
|
||||
};
|
||||
$.info(`正在更新组合订阅:${name}...`);
|
||||
|
||||
if (name !== newCol.name) {
|
||||
// update all artifacts referring this collection
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY) || [];
|
||||
for (const artifact of allArtifacts) {
|
||||
if (
|
||||
artifact.type === 'collection' &&
|
||||
artifact.source === oldCol.name
|
||||
) {
|
||||
artifact.source = newCol.name;
|
||||
}
|
||||
}
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
}
|
||||
|
||||
updateByName(allCols, name, newCol);
|
||||
$.write(allCols, COLLECTIONS_KEY);
|
||||
success(res, newCol);
|
||||
|
||||
@@ -48,7 +48,7 @@ async function downloadSubscription(req, res) {
|
||||
}
|
||||
} catch (err) {
|
||||
$.notify(
|
||||
`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载订阅失败`,
|
||||
`🌍 Sub-Store 下载订阅失败`,
|
||||
`❌ 无法下载订阅:${name}!`,
|
||||
`🤔 原因:${JSON.stringify(err)}`,
|
||||
);
|
||||
@@ -63,7 +63,7 @@ async function downloadSubscription(req, res) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$.notify(`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载订阅失败`, `❌ 未找到订阅:${name}!`);
|
||||
$.notify(`🌍 Sub-Store 下载订阅失败`, `❌ 未找到订阅:${name}!`);
|
||||
failed(
|
||||
res,
|
||||
new ResourceNotFoundError(
|
||||
@@ -117,7 +117,7 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
} catch (err) {
|
||||
$.notify(
|
||||
`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载组合订阅失败`,
|
||||
`🌍 Sub-Store 下载组合订阅失败`,
|
||||
`❌ 下载组合订阅错误:${name}!`,
|
||||
`🤔 原因:${err}`,
|
||||
);
|
||||
@@ -132,7 +132,7 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
} else {
|
||||
$.notify(
|
||||
`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载组合订阅失败`,
|
||||
`🌍 Sub-Store 下载组合订阅失败`,
|
||||
`❌ 未找到组合订阅:${name}!`,
|
||||
);
|
||||
failed(
|
||||
|
||||
@@ -14,11 +14,20 @@ import registerSubscriptionRoutes from './subscriptions';
|
||||
import registerCollectionRoutes from './collections';
|
||||
import registerArtifactRoutes from './artifacts';
|
||||
import registerDownloadRoutes from './download';
|
||||
import registerSettingRoutes from './settings';
|
||||
import registerSettingRoutes, {
|
||||
updateArtifactStore,
|
||||
updateGitHubAvatar,
|
||||
} from './settings';
|
||||
import registerPreviewRoutes from './preview';
|
||||
import registerSortingRoutes from './sort';
|
||||
import { failed, success } from '@/restful/response';
|
||||
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
|
||||
import {
|
||||
InternalServerError,
|
||||
NetworkError,
|
||||
RequestInvalidError,
|
||||
} from '@/restful/errors';
|
||||
import resourceCache from '@/utils/resource-cache';
|
||||
import producer from '@/core/proxy-utils/producers';
|
||||
|
||||
export default function serve() {
|
||||
const $app = express({ substore: $ });
|
||||
@@ -33,9 +42,10 @@ export default function serve() {
|
||||
registerArtifactRoutes($app);
|
||||
|
||||
// utils
|
||||
$app.get('/api/utils/IP_API/:server', IP_API); // IP-API reverse proxy
|
||||
$app.post('/api/utils/node-info', getNodeInfo);
|
||||
$app.get('/api/utils/env', getEnv); // get runtime environment
|
||||
$app.get('/api/utils/backup', gistBackup); // gist backup actions
|
||||
$app.get('/api/utils/refresh', refresh);
|
||||
|
||||
// Storage management
|
||||
$app.route('/api/storage')
|
||||
@@ -84,6 +94,16 @@ function getEnv(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh(_, res) {
|
||||
// 1. get GitHub avatar and artifact store
|
||||
await updateGitHubAvatar();
|
||||
await updateArtifactStore();
|
||||
|
||||
// 2. clear resource cache
|
||||
resourceCache.revokeAll();
|
||||
success(res);
|
||||
}
|
||||
|
||||
async function gistBackup(req, res) {
|
||||
const { action } = req.query;
|
||||
// read token
|
||||
@@ -153,11 +173,50 @@ async function gistBackup(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function IP_API(req, res) {
|
||||
const server = decodeURIComponent(req.params.server);
|
||||
const $http = HTTP();
|
||||
const result = await $http
|
||||
.get(`http://ip-api.com/json/${server}?lang=zh-CN`)
|
||||
.then((resp) => JSON.parse(resp.body));
|
||||
res.json(result);
|
||||
async function getNodeInfo(req, res) {
|
||||
const proxy = req.body;
|
||||
const lang = req.query.lang || 'zh-CN';
|
||||
let shareUrl;
|
||||
try {
|
||||
shareUrl = producer.URI.produce(proxy);
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
try {
|
||||
const $http = HTTP();
|
||||
const info = await $http
|
||||
.get({
|
||||
url: `http://ip-api.com/json/${encodeURIComponent(
|
||||
proxy.server,
|
||||
)}?lang=${lang}`,
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15',
|
||||
},
|
||||
})
|
||||
.then((resp) => {
|
||||
const data = JSON.parse(resp.body);
|
||||
if (data.status !== 'success') {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
|
||||
// remove unnecessary fields
|
||||
delete data.status;
|
||||
return data;
|
||||
});
|
||||
success(res, {
|
||||
shareUrl,
|
||||
info,
|
||||
});
|
||||
} catch (err) {
|
||||
failed(
|
||||
res,
|
||||
new NetworkError(
|
||||
'FAILED_TO_GET_NODE_INFO',
|
||||
`Failed to get node info`,
|
||||
`Reason: ${err}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ async function updateSettings(req, res) {
|
||||
success(res, newSettings);
|
||||
}
|
||||
|
||||
async function updateGitHubAvatar() {
|
||||
export async function updateGitHubAvatar() {
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
const username = settings.githubUser;
|
||||
if (username) {
|
||||
@@ -50,8 +50,8 @@ async function updateGitHubAvatar() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateArtifactStore() {
|
||||
console.log('Updating artifact store');
|
||||
export async function updateArtifactStore() {
|
||||
$.log('Updating artifact store');
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
const { githubUser, gistToken } = settings;
|
||||
if (githubUser && gistToken) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
RequestInvalidError,
|
||||
} from './errors';
|
||||
import { deleteByName, findByName, updateByName } from '@/utils/database';
|
||||
import { SUBS_KEY, COLLECTIONS_KEY } from '@/constants';
|
||||
import { SUBS_KEY, COLLECTIONS_KEY, ARTIFACTS_KEY } from '@/constants';
|
||||
import { getFlowHeaders } from '@/utils/flow';
|
||||
import { success, failed } from './response';
|
||||
import $ from '@/core/app';
|
||||
@@ -137,14 +137,28 @@ function updateSubscription(req, res) {
|
||||
$.info(`正在更新订阅: ${name}`);
|
||||
// allow users to update the subscription name
|
||||
if (name !== sub.name) {
|
||||
// we need to find out all collections refer to this name
|
||||
const allCols = $.read(COLLECTIONS_KEY);
|
||||
// update all collections refer to this name
|
||||
const allCols = $.read(COLLECTIONS_KEY) || [];
|
||||
for (const collection of allCols) {
|
||||
const idx = collection.subscriptions.indexOf(name);
|
||||
if (idx !== -1) {
|
||||
collection.subscriptions[idx] = sub.name;
|
||||
}
|
||||
}
|
||||
|
||||
// update all artifacts referring this subscription
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY) || [];
|
||||
for (const artifact of allArtifacts) {
|
||||
if (
|
||||
artifact.type === 'subscription' &&
|
||||
artifact.source == name
|
||||
) {
|
||||
artifact.source = sub.name;
|
||||
}
|
||||
}
|
||||
|
||||
$.write(allCols, COLLECTIONS_KEY);
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
}
|
||||
updateByName(allSubs, name, newSub);
|
||||
$.write(allSubs, SUBS_KEY);
|
||||
|
||||
@@ -251,7 +251,7 @@ function createTestCases() {
|
||||
server,
|
||||
port,
|
||||
uuid,
|
||||
cipher: 'auto', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
cipher: 'none', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
alterId: 0,
|
||||
},
|
||||
},
|
||||
@@ -288,7 +288,7 @@ function createTestCases() {
|
||||
server,
|
||||
port,
|
||||
uuid,
|
||||
cipher: 'auto', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
cipher: 'none', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
alterId: 0,
|
||||
aead: true,
|
||||
},
|
||||
@@ -339,7 +339,7 @@ function createTestCases() {
|
||||
server,
|
||||
port,
|
||||
uuid,
|
||||
cipher: 'auto', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
cipher: 'none', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
@@ -402,7 +402,7 @@ function createTestCases() {
|
||||
server,
|
||||
port,
|
||||
uuid,
|
||||
cipher: 'auto', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
cipher: 'none', // Surge lacks support for specifying cipher for vmess protocol!
|
||||
network: 'ws',
|
||||
'ws-opts': {
|
||||
path: obfs_path,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { HTTP } from '@/vendor/open-api';
|
||||
import { hex_md5 } from '@/vendor/md5';
|
||||
import resourceCache from '@/utils/resource-cache';
|
||||
|
||||
const cache = new Map();
|
||||
const tasks = new Map();
|
||||
|
||||
export default async function download(url, ua) {
|
||||
ua = ua || 'Quantumult%20X/1.0.29 (iPhone14,5; iOS 15.4.1)';
|
||||
const id = ua + url;
|
||||
if (cache.has(id)) {
|
||||
return cache.get(id);
|
||||
const id = hex_md5(ua + url);
|
||||
if (tasks.has(id)) {
|
||||
return tasks.get(id);
|
||||
}
|
||||
|
||||
const http = HTTP({
|
||||
@@ -16,18 +18,27 @@ export default async function download(url, ua) {
|
||||
});
|
||||
|
||||
const result = new Promise((resolve, reject) => {
|
||||
http.get(url)
|
||||
.then((resp) => {
|
||||
const body = resp.body;
|
||||
if (body.replace(/\s/g, '').length === 0)
|
||||
reject(new Error('订阅内容为空!'));
|
||||
else resolve(body);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error(`无法下载 URL:${url}`));
|
||||
});
|
||||
// try to find in app cache
|
||||
const cached = resourceCache.get(id);
|
||||
if (cached) {
|
||||
resolve(cached);
|
||||
} else {
|
||||
http.get(url)
|
||||
.then((resp) => {
|
||||
const body = resp.body;
|
||||
if (body.replace(/\s/g, '').length === 0)
|
||||
reject(new Error('远程资源内容为空!'));
|
||||
else {
|
||||
resourceCache.set(id, body);
|
||||
resolve(body);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error(`无法下载 URL:${url}`));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cache.set(id, result);
|
||||
tasks.set(id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ function doMigrationV2() {
|
||||
useless: 'DEFAULT',
|
||||
},
|
||||
};
|
||||
processes.forEach((p) => {
|
||||
for (const p of processes) {
|
||||
if (!p.type) continue;
|
||||
if (p.type === 'Useless Filter') {
|
||||
quickSettingOperator.args.useless = 'ENABLED';
|
||||
} else if (p.type === 'Set Property Operator') {
|
||||
@@ -120,7 +121,7 @@ function doMigrationV2() {
|
||||
} else {
|
||||
newProcesses.push(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
newProcesses.unshift(quickSettingOperator);
|
||||
item.process = newProcesses;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getPlatformFromHeaders(headers) {
|
||||
} else if (UA.indexOf('Decar') !== -1 || UA.indexOf('Loon') !== -1) {
|
||||
return 'Loon';
|
||||
} else if (UA.indexOf('Shadowrocket') !== -1) {
|
||||
return 'Clash';
|
||||
return 'ShadowRocket';
|
||||
} else if (UA.indexOf('Stash') !== -1) {
|
||||
return 'Stash';
|
||||
} else {
|
||||
|
||||
55
backend/src/utils/resource-cache.js
Normal file
55
backend/src/utils/resource-cache.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import $ from '@/core/app';
|
||||
import { CACHE_EXPIRATION_TIME_MS, RESOURCE_CACHE_KEY } from '@/constants';
|
||||
|
||||
class ResourceCache {
|
||||
constructor(expires) {
|
||||
this.expires = expires;
|
||||
if (!$.read(RESOURCE_CACHE_KEY)) {
|
||||
$.write('{}', RESOURCE_CACHE_KEY);
|
||||
}
|
||||
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
_cleanup() {
|
||||
// clear obsolete cached resource
|
||||
let clear = false;
|
||||
Object.entries(this.resourceCache).forEach((entry) => {
|
||||
const [id, updated] = entry;
|
||||
if (new Date().getTime() - updated > this.expires) {
|
||||
$.delete(`#${id}`);
|
||||
delete this.resourceCache[id];
|
||||
clear = true;
|
||||
}
|
||||
});
|
||||
if (clear) this._persist();
|
||||
}
|
||||
|
||||
revokeAll() {
|
||||
Object.keys(this.resourceCache).forEach((id) => {
|
||||
$.delete(`#${id}`);
|
||||
});
|
||||
this.resourceCache = {};
|
||||
this._persist();
|
||||
}
|
||||
|
||||
_persist() {
|
||||
$.write(JSON.stringify(this.resourceCache), RESOURCE_CACHE_KEY);
|
||||
}
|
||||
|
||||
get(id) {
|
||||
const updated = this.resourceCache[id];
|
||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||
return $.read(`#${id}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
set(id, value) {
|
||||
this.resourceCache[id] = new Date().getTime();
|
||||
this._persist();
|
||||
$.write(value, `#${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ResourceCache(CACHE_EXPIRATION_TIME_MS);
|
||||
387
backend/src/vendor/md5.js
vendored
Normal file
387
backend/src/vendor/md5.js
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Configurable variables. You may need to tweak these to be compatible with
|
||||
* the server-side, but the defaults work in most cases.
|
||||
*/
|
||||
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
|
||||
var b64pad = ''; /* base-64 pad character. "=" for strict RFC compliance */
|
||||
|
||||
/*
|
||||
* These are the functions you'll usually want to call
|
||||
* They take string arguments and return either hex or base-64 encoded strings
|
||||
*/
|
||||
export function hex_md5(s) {
|
||||
return rstr2hex(rstr_md5(str2rstr_utf8(s)));
|
||||
}
|
||||
|
||||
export function b64_md5(s) {
|
||||
return rstr2b64(rstr_md5(str2rstr_utf8(s)));
|
||||
}
|
||||
|
||||
export function any_md5(s, e) {
|
||||
return rstr2any(rstr_md5(str2rstr_utf8(s)), e);
|
||||
}
|
||||
|
||||
export function hex_hmac_md5(k, d) {
|
||||
return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)));
|
||||
}
|
||||
|
||||
export function b64_hmac_md5(k, d) {
|
||||
return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)));
|
||||
}
|
||||
|
||||
export function any_hmac_md5(k, d, e) {
|
||||
return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a simple self-test to see if the VM is working
|
||||
*/
|
||||
function md5_vm_test() {
|
||||
return hex_md5('abc').toLowerCase() == '900150983cd24fb0d6963f7d28e17f72';
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of a raw string
|
||||
*/
|
||||
function rstr_md5(s) {
|
||||
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the HMAC-MD5, of a key and some data (raw strings)
|
||||
*/
|
||||
function rstr_hmac_md5(key, data) {
|
||||
var bkey = rstr2binl(key);
|
||||
if (bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
|
||||
|
||||
var ipad = Array(16),
|
||||
opad = Array(16);
|
||||
for (var i = 0; i < 16; i++) {
|
||||
ipad[i] = bkey[i] ^ 0x36363636;
|
||||
opad[i] = bkey[i] ^ 0x5c5c5c5c;
|
||||
}
|
||||
|
||||
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
|
||||
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to a hex string
|
||||
*/
|
||||
function rstr2hex(input) {
|
||||
try {
|
||||
hexcase;
|
||||
} catch (e) {
|
||||
hexcase = 0;
|
||||
}
|
||||
var hex_tab = hexcase ? '0123456789ABCDEF' : '0123456789abcdef';
|
||||
var output = '';
|
||||
var x;
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
x = input.charCodeAt(i);
|
||||
output += hex_tab.charAt((x >>> 4) & 0x0f) + hex_tab.charAt(x & 0x0f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to a base-64 string
|
||||
*/
|
||||
function rstr2b64(input) {
|
||||
try {
|
||||
b64pad;
|
||||
} catch (e) {
|
||||
b64pad = '';
|
||||
}
|
||||
var tab =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
var output = '';
|
||||
var len = input.length;
|
||||
for (var i = 0; i < len; i += 3) {
|
||||
var triplet =
|
||||
(input.charCodeAt(i) << 16) |
|
||||
(i + 1 < len ? input.charCodeAt(i + 1) << 8 : 0) |
|
||||
(i + 2 < len ? input.charCodeAt(i + 2) : 0);
|
||||
for (var j = 0; j < 4; j++) {
|
||||
if (i * 8 + j * 6 > input.length * 8) output += b64pad;
|
||||
else output += tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to an arbitrary string encoding
|
||||
*/
|
||||
function rstr2any(input, encoding) {
|
||||
var divisor = encoding.length;
|
||||
var i, j, q, x, quotient;
|
||||
|
||||
/* Convert to an array of 16-bit big-endian values, forming the dividend */
|
||||
var dividend = Array(Math.ceil(input.length / 2));
|
||||
for (i = 0; i < dividend.length; i++) {
|
||||
dividend[i] =
|
||||
(input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Repeatedly perform a long division. The binary array forms the dividend,
|
||||
* the length of the encoding is the divisor. Once computed, the quotient
|
||||
* forms the dividend for the next step. All remainders are stored for later
|
||||
* use.
|
||||
*/
|
||||
var full_length = Math.ceil(
|
||||
(input.length * 8) / (Math.log(encoding.length) / Math.log(2)),
|
||||
);
|
||||
var remainders = Array(full_length);
|
||||
for (j = 0; j < full_length; j++) {
|
||||
quotient = Array();
|
||||
x = 0;
|
||||
for (i = 0; i < dividend.length; i++) {
|
||||
x = (x << 16) + dividend[i];
|
||||
q = Math.floor(x / divisor);
|
||||
x -= q * divisor;
|
||||
if (quotient.length > 0 || q > 0) quotient[quotient.length] = q;
|
||||
}
|
||||
remainders[j] = x;
|
||||
dividend = quotient;
|
||||
}
|
||||
|
||||
/* Convert the remainders to the output string */
|
||||
var output = '';
|
||||
for (i = remainders.length - 1; i >= 0; i--)
|
||||
output += encoding.charAt(remainders[i]);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a string as utf-8.
|
||||
* For efficiency, this assumes the input is valid utf-16.
|
||||
*/
|
||||
function str2rstr_utf8(input) {
|
||||
var output = '';
|
||||
var i = -1;
|
||||
var x, y;
|
||||
|
||||
while (++i < input.length) {
|
||||
/* Decode utf-16 surrogate pairs */
|
||||
x = input.charCodeAt(i);
|
||||
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
|
||||
if (0xd800 <= x && x <= 0xdbff && 0xdc00 <= y && y <= 0xdfff) {
|
||||
x = 0x10000 + ((x & 0x03ff) << 10) + (y & 0x03ff);
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Encode output as utf-8 */
|
||||
if (x <= 0x7f) output += String.fromCharCode(x);
|
||||
else if (x <= 0x7ff)
|
||||
output += String.fromCharCode(
|
||||
0xc0 | ((x >>> 6) & 0x1f),
|
||||
0x80 | (x & 0x3f),
|
||||
);
|
||||
else if (x <= 0xffff)
|
||||
output += String.fromCharCode(
|
||||
0xe0 | ((x >>> 12) & 0x0f),
|
||||
0x80 | ((x >>> 6) & 0x3f),
|
||||
0x80 | (x & 0x3f),
|
||||
);
|
||||
else if (x <= 0x1fffff)
|
||||
output += String.fromCharCode(
|
||||
0xf0 | ((x >>> 18) & 0x07),
|
||||
0x80 | ((x >>> 12) & 0x3f),
|
||||
0x80 | ((x >>> 6) & 0x3f),
|
||||
0x80 | (x & 0x3f),
|
||||
);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a string as utf-16
|
||||
*/
|
||||
function str2rstr_utf16le(input) {
|
||||
var output = '';
|
||||
for (var i = 0; i < input.length; i++)
|
||||
output += String.fromCharCode(
|
||||
input.charCodeAt(i) & 0xff,
|
||||
(input.charCodeAt(i) >>> 8) & 0xff,
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
function str2rstr_utf16be(input) {
|
||||
var output = '';
|
||||
for (var i = 0; i < input.length; i++)
|
||||
output += String.fromCharCode(
|
||||
(input.charCodeAt(i) >>> 8) & 0xff,
|
||||
input.charCodeAt(i) & 0xff,
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to an array of little-endian words
|
||||
* Characters >255 have their high-byte silently ignored.
|
||||
*/
|
||||
function rstr2binl(input) {
|
||||
var output = Array(input.length >> 2);
|
||||
for (var i = 0; i < output.length; i++) output[i] = 0;
|
||||
for (var i = 0; i < input.length * 8; i += 8)
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of little-endian words to a string
|
||||
*/
|
||||
function binl2rstr(input) {
|
||||
var output = '';
|
||||
for (var i = 0; i < input.length * 32; i += 8)
|
||||
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of an array of little-endian words, and a bit length.
|
||||
*/
|
||||
function binl_md5(x, len) {
|
||||
/* append padding */
|
||||
x[len >> 5] |= 0x80 << len % 32;
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
|
||||
var a = 1732584193;
|
||||
var b = -271733879;
|
||||
var c = -1732584194;
|
||||
var d = 271733878;
|
||||
|
||||
for (var i = 0; i < x.length; i += 16) {
|
||||
var olda = a;
|
||||
var oldb = b;
|
||||
var oldc = c;
|
||||
var oldd = d;
|
||||
|
||||
a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
|
||||
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
|
||||
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
|
||||
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
||||
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
|
||||
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
||||
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
||||
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
|
||||
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
||||
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
||||
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
|
||||
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
||||
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
||||
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
|
||||
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
||||
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
||||
|
||||
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
|
||||
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
||||
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
|
||||
b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
|
||||
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
|
||||
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
|
||||
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
|
||||
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
|
||||
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
|
||||
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
||||
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
|
||||
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
||||
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
||||
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
|
||||
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
||||
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
||||
|
||||
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
|
||||
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
||||
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
||||
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
|
||||
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
||||
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
||||
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
|
||||
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
||||
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
|
||||
d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
|
||||
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
|
||||
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
|
||||
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
|
||||
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
|
||||
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
|
||||
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
|
||||
|
||||
a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
|
||||
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
||||
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
||||
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
|
||||
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
||||
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
||||
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
|
||||
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
||||
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
||||
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
|
||||
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
||||
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
||||
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
|
||||
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
||||
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
|
||||
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
|
||||
|
||||
a = safe_add(a, olda);
|
||||
b = safe_add(b, oldb);
|
||||
c = safe_add(c, oldc);
|
||||
d = safe_add(d, oldd);
|
||||
}
|
||||
return Array(a, b, c, d);
|
||||
}
|
||||
|
||||
/*
|
||||
* These functions implement the four basic operations the algorithm uses.
|
||||
*/
|
||||
function md5_cmn(q, a, b, x, s, t) {
|
||||
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
|
||||
}
|
||||
|
||||
function md5_ff(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & c) | (~b & d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function md5_gg(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & d) | (c & ~d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function md5_hh(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
}
|
||||
|
||||
function md5_ii(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(c ^ (b | ~d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
|
||||
* to work around bugs in some JS interpreters.
|
||||
*/
|
||||
function safe_add(x, y) {
|
||||
var lsw = (x & 0xffff) + (y & 0xffff);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*/
|
||||
function bit_rol(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
33
backend/src/vendor/open-api.js
vendored
33
backend/src/vendor/open-api.js
vendored
@@ -51,24 +51,20 @@ export class OpenAPI {
|
||||
// create a json for root cache
|
||||
let fpath = 'root.json';
|
||||
if (!this.node.fs.existsSync(fpath)) {
|
||||
this.node.fs.writeFileSync(
|
||||
fpath,
|
||||
JSON.stringify({}),
|
||||
{ flag: 'wx' },
|
||||
(err) => console.log(err),
|
||||
);
|
||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
||||
flag: 'wx',
|
||||
});
|
||||
this.root = {};
|
||||
} else {
|
||||
this.root = JSON.parse(this.node.fs.readFileSync(`${fpath}`));
|
||||
}
|
||||
this.root = {};
|
||||
|
||||
// create a json file with the given name if not exists
|
||||
fpath = `${this.name}.json`;
|
||||
if (!this.node.fs.existsSync(fpath)) {
|
||||
this.node.fs.writeFileSync(
|
||||
fpath,
|
||||
JSON.stringify({}),
|
||||
{ flag: 'wx' },
|
||||
(err) => console.log(err),
|
||||
);
|
||||
this.node.fs.writeFileSync(fpath, JSON.stringify({}), {
|
||||
flag: 'wx',
|
||||
});
|
||||
this.cache = {};
|
||||
} else {
|
||||
this.cache = JSON.parse(
|
||||
@@ -257,6 +253,17 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||
|
||||
events.onRequest(method, options);
|
||||
|
||||
if (options.node) {
|
||||
// Surge & Loon allow connecting to a server using a specified proxy node
|
||||
if (isSurge) {
|
||||
const build = $environment['surge-build'];
|
||||
if (build && parseInt(build) >= 2407) {
|
||||
options['policy-descriptor'] = options.node;
|
||||
delete options.node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let worker;
|
||||
if (isQX) {
|
||||
worker = $task.fetch({
|
||||
|
||||
6
backend/sub-store.min.js
vendored
6
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -9,4 +9,6 @@
|
||||
hostname=sub.store
|
||||
|
||||
[Script]
|
||||
http-request https?:\/\/sub\.store script-path=https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js, requires-body=true, timeout=120, tag=Sub-Store
|
||||
http-request https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.min.js, requires-body=true, timeout=120, tag=Sub-Store
|
||||
|
||||
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
|
||||
@@ -1,3 +1,3 @@
|
||||
hostname=sub.store
|
||||
|
||||
^https?:\/\/sub\.store url script-analyze-echo-response https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js
|
||||
^https?:\/\/sub\.store url script-analyze-echo-response https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.min.js
|
||||
@@ -10,7 +10,18 @@ http:
|
||||
type: request
|
||||
require-body: true
|
||||
timeout: 120
|
||||
|
||||
cron:
|
||||
script:
|
||||
- name: cron-sync-artifacts
|
||||
cron: "0 0 * * *"
|
||||
timeout: 120
|
||||
|
||||
script-providers:
|
||||
sub-store:
|
||||
url: https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js
|
||||
url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.min.js
|
||||
interval: 86400
|
||||
|
||||
cron-sync-artifacts:
|
||||
url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
||||
interval: 86400
|
||||
@@ -4,4 +4,6 @@
|
||||
hostname=%APPEND% sub.store
|
||||
|
||||
[Script]
|
||||
Sub-Store = type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js,requires-body=true,timeout=120,max-size=131072
|
||||
Sub-Store = type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.min.js,requires-body=true,timeout=120,max-size=131072,ability=http-client-policy
|
||||
|
||||
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
|
||||
|
||||
174
scripts/ip-flag.js
Normal file
174
scripts/ip-flag.js
Normal file
@@ -0,0 +1,174 @@
|
||||
const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
|
||||
const CACHE_EXPIRATION_TIME_MS = 10 * 60 * 1000;
|
||||
const $ = $substore;
|
||||
|
||||
class ResourceCache {
|
||||
constructor(expires) {
|
||||
this.expires = expires;
|
||||
if (!$.read(RESOURCE_CACHE_KEY)) {
|
||||
$.write('{}', RESOURCE_CACHE_KEY);
|
||||
}
|
||||
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
_cleanup() {
|
||||
// clear obsolete cached resource
|
||||
let clear = false;
|
||||
Object.entries(this.resourceCache).forEach((entry) => {
|
||||
const [id, updated] = entry;
|
||||
if (new Date().getTime() - updated > this.expires) {
|
||||
$.delete(`#${id}`);
|
||||
delete this.resourceCache[id];
|
||||
clear = true;
|
||||
}
|
||||
});
|
||||
if (clear) this._persist();
|
||||
}
|
||||
|
||||
revokeAll() {
|
||||
Object.keys(this.resourceCache).forEach((id) => {
|
||||
$.delete(`#${id}`);
|
||||
});
|
||||
this.resourceCache = {};
|
||||
this._persist();
|
||||
}
|
||||
|
||||
_persist() {
|
||||
$.write(JSON.stringify(this.resourceCache), RESOURCE_CACHE_KEY);
|
||||
}
|
||||
|
||||
get(id) {
|
||||
const updated = this.resourceCache[id];
|
||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||
return $.read(`#${id}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
set(id, value) {
|
||||
this.resourceCache[id] = new Date().getTime();
|
||||
this._persist();
|
||||
$.write(value, `#${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
const resourceCache = new ResourceCache(CACHE_EXPIRATION_TIME_MS);
|
||||
|
||||
async function operator(proxies) {
|
||||
const { isLoon, isSurge } = $substore.env;
|
||||
let support = false;
|
||||
if (isLoon) {
|
||||
support = true;
|
||||
} else if (isSurge) {
|
||||
const build = $environment['surge-build'];
|
||||
if (build && parseInt(build) >= 2407) {
|
||||
support = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (support) {
|
||||
const batches = [];
|
||||
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 => {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
$.error(`IP Flag only supports Loon and Surge!`);
|
||||
}
|
||||
return proxies;
|
||||
}
|
||||
|
||||
const tasks = new Map();
|
||||
async function queryIpApi(proxy) {
|
||||
const id = getId(proxy);
|
||||
if (tasks.has(id)) {
|
||||
return tasks.get(id);
|
||||
}
|
||||
|
||||
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 { isLoon } = $substore.env;
|
||||
const target = isLoon ? "Loon" : "Surge";
|
||||
const result = new Promise((resolve, reject) => {
|
||||
const cached = resourceCache.get(id);
|
||||
if (cached) {
|
||||
resolve(cached);
|
||||
}
|
||||
const url = `http://ip-api.com/json`;
|
||||
let node = ProxyUtils.produce([proxy], target);
|
||||
|
||||
// Loon 需要去掉节点名字
|
||||
if (isLoon) {
|
||||
const s = node.indexOf("=");
|
||||
node = node.substring(s + 1);
|
||||
}
|
||||
|
||||
$.http.get({
|
||||
url,
|
||||
headers,
|
||||
node
|
||||
}).then(resp => {
|
||||
const body = resp.body;
|
||||
const data = JSON.parse(body);
|
||||
if (data.status === "success") {
|
||||
resourceCache.set(id, data.countryCode);
|
||||
resolve(data.countryCode);
|
||||
} else {
|
||||
reject(new Error(data.message));
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
tasks.set(id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function getId(proxy) {
|
||||
return MD5(`IP-FLAG-${proxy.server}-${proxy.port}`);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
var MD5 = function (d) { var r = M(V(Y(X(d), 8 * d.length))); return r.toLowerCase() }; function M(d) { for (var _, m = "0123456789ABCDEF", f = "", r = 0; r < d.length; r++)_ = d.charCodeAt(r), f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _); return f } function X(d) { for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++)_[m] = 0; for (m = 0; m < 8 * d.length; m += 8)_[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32; return _ } function V(d) { for (var _ = "", m = 0; m < 32 * d.length; m += 8)_ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255); return _ } function Y(d, _) { d[_ >> 5] |= 128 << _ % 32, d[14 + (_ + 64 >>> 9 << 4)] = _; for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) { var h = m, t = f, g = r, e = i; f = md5_ii(f = md5_ii(f = md5_ii(f = md5_ii(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_ff(f = md5_ff(f = md5_ff(f = md5_ff(f, r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = safe_add(m, h), f = safe_add(f, t), r = safe_add(r, g), i = safe_add(i, e) } return Array(m, f, r, i) } function md5_cmn(d, _, m, f, r, i) { return safe_add(bit_rol(safe_add(safe_add(_, d), safe_add(f, i)), r), m) } function md5_ff(d, _, m, f, r, i, n) { return md5_cmn(_ & m | ~_ & f, d, _, r, i, n) } function md5_gg(d, _, m, f, r, i, n) { return md5_cmn(_ & f | m & ~f, d, _, r, i, n) } function md5_hh(d, _, m, f, r, i, n) { return md5_cmn(_ ^ m ^ f, d, _, r, i, n) } function md5_ii(d, _, m, f, r, i, n) { return md5_cmn(m ^ (_ | ~f), d, _, r, i, n) } function safe_add(d, _) { var m = (65535 & d) + (65535 & _); return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m } function bit_rol(d, _) { return d << _ | d >>> 32 - _ }
|
||||
@@ -1,11 +1,5 @@
|
||||
const $ = API("sub-store");
|
||||
$.write({}, "subs");
|
||||
$.write({}, "collections");
|
||||
$.write({}, "artifacts");
|
||||
const $ = API()
|
||||
$.write("{}", "#sub-store")
|
||||
$.done()
|
||||
|
||||
$.done();
|
||||
|
||||
// prettier-ignore
|
||||
/*********************************** API *************************************/
|
||||
function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!t,o="function"==typeof require&&"undefined"!=typeof $jsbox;return{isQX:e,isLoon:t,isSurge:s,isNode:"function"==typeof require&&!o,isJSBox:o,isRequest:"undefined"!=typeof $request,isScriptable:"undefined"!=typeof importModule}}function HTTP(e,t={}){const{isQX:s,isLoon:o,isSurge:i,isScriptable:n,isNode:r}=ENV();const u={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(c=>u[c.toLowerCase()]=(u=>(function(u,c){(c="string"==typeof c?{url:c}:c).url=e?e+c.url:c.url;const h=(c={...t,...c}).timeout,l={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...c.events};let a,d;if(l.onRequest(u,c),s)a=$task.fetch({method:u,...c});else if(o||i||r)a=new Promise((e,t)=>{(r?require("request"):$httpClient)[u.toLowerCase()](c,(s,o,i)=>{s?t(s):e({statusCode:o.status||o.statusCode,headers:o.headers,body:i})})});else if(n){const e=new Request(c.url);e.method=u,e.headers=c.headers,e.body=c.body,a=new Promise((t,s)=>{e.loadString().then(s=>{t({statusCode:e.response.statusCode,headers:e.response.headers,body:s})}).catch(e=>s(e))})}const f=h?new Promise((e,t)=>{d=setTimeout(()=>(l.onTimeout(),t(`${u} URL: ${c.url} exceeds the timeout ${h} ms`)),h)}):null;return(f?Promise.race([f,a]).then(e=>(clearTimeout(d),e)):a).then(e=>l.onResponse(e))})(c,u))),u}function API(e="untitled",t=!1){const{isQX:s,isLoon:o,isSurge:i,isNode:n,isJSBox:r,isScriptable:u}=ENV();return new class{constructor(e,t){this.name=e,this.debug=t,this.http=HTTP(),this.env=ENV(),this.node=(()=>{if(n){return{fs:require("fs")}}return null})(),this.initCache();Promise.prototype.delay=function(e){return this.then(function(t){return((e,t)=>new Promise(function(s){setTimeout(s.bind(null,t),e)}))(e,t)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||"{}")),(o||i)&&(this.cache=JSON.parse($persistentStore.read(this.name)||"{}")),n){let e="root.json";this.node.fs.existsSync(e)||this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.root={},e=`${this.name}.json`,this.node.fs.existsSync(e)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.cache={})}}persistCache(){const e=JSON.stringify(this.cache);s&&$prefs.setValueForKey(e,this.name),(o||i)&&$persistentStore.write(e,this.name),n&&(this.node.fs.writeFileSync(`${this.name}.json`,e,{flag:"w"},e=>console.log(e)),this.node.fs.writeFileSync("root.json",JSON.stringify(this.root),{flag:"w"},e=>console.log(e)))}write(e,t){this.log(`SET ${t}`),-1!==t.indexOf("#")?(t=t.substr(1),i&o&&$persistentStore.write(e,t),s&&$prefs.setValueForKey(e,t),n&&(this.root[t]=e)):this.cache[t]=e,this.persistCache()}read(e){return this.log(`READ ${e}`),-1===e.indexOf("#")?this.cache[e]:(e=e.substr(1),i&o?$persistentStore.read(e):s?$prefs.valueForKey(e):n?this.root[e]:void 0)}delete(e){this.log(`DELETE ${e}`),-1!==e.indexOf("#")?(e=e.substr(1),i&o&&$persistentStore.write(null,e),s&&$prefs.removeValueForKey(e),n&&delete this.root[e]):delete this.cache[e],this.persistCache()}notify(e,t="",c="",h={}){const l=h["open-url"],a=h["media-url"];if(s&&$notify(e,t,c,h),i&&$notification.post(e,t,c+`${a?"\n多媒体:"+a:""}`,{url:l}),o){let s={};l&&(s.openUrl=l),a&&(s.mediaUrl=a),"{}"==JSON.stringify(s)?$notification.post(e,t,c):$notification.post(e,t,c,s)}if(n||u){const s=c+(l?`\n点击跳转: ${l}`:"")+(a?`\n多媒体: ${a}`:"");if(r){require("push").schedule({title:e,body:(t?t+"\n":"")+s})}else console.log(`${e}\n${t}\n${s}\n\n`)}}log(e){this.debug&&console.log(e)}info(e){console.log(e)}error(e){console.log("ERROR: "+e)}wait(e){return new Promise(t=>setTimeout(t,e))}done(e={}){s||o||i?$done(e):n&&!r&&"undefined"!=typeof $context&&($context.headers=e.headers,$context.statusCode=e.statusCode,$context.body=e.body)}}(e,t)}
|
||||
/*****************************************************************************/
|
||||
function ENV(){const e="function"==typeof require&&"undefined"!=typeof $jsbox;return{isQX:"undefined"!=typeof $task,isLoon:"undefined"!=typeof $loon,isSurge:"undefined"!=typeof $httpClient&&"undefined"!=typeof $utils,isBrowser:"undefined"!=typeof document,isNode:"function"==typeof require&&!e,isJSBox:e,isRequest:"undefined"!=typeof $request,isScriptable:"undefined"!=typeof importModule}}function HTTP(e={baseURL:""}){const{isQX:t,isLoon:s,isSurge:o,isScriptable:n,isNode:i,isBrowser:r}=ENV(),u=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;const a={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(h=>a[h.toLowerCase()]=(a=>(function(a,h){h="string"==typeof h?{url:h}:h;const d=e.baseURL;d&&!u.test(h.url||"")&&(h.url=d?d+h.url:h.url),h.body&&h.headers&&!h.headers["Content-Type"]&&(h.headers["Content-Type"]="application/x-www-form-urlencoded");const l=(h={...e,...h}).timeout,c={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...h.events};let f,p;if(c.onRequest(a,h),t)f=$task.fetch({method:a,...h});else if(s||o||i)f=new Promise((e,t)=>{(i?require("request"):$httpClient)[a.toLowerCase()](h,(s,o,n)=>{s?t(s):e({statusCode:o.status||o.statusCode,headers:o.headers,body:n})})});else if(n){const e=new Request(h.url);e.method=a,e.headers=h.headers,e.body=h.body,f=new Promise((t,s)=>{e.loadString().then(s=>{t({statusCode:e.response.statusCode,headers:e.response.headers,body:s})}).catch(e=>s(e))})}else r&&(f=new Promise((e,t)=>{fetch(h.url,{method:a,headers:h.headers,body:h.body}).then(e=>e.json()).then(t=>e({statusCode:t.status,headers:t.headers,body:t.data})).catch(t)}));const y=l?new Promise((e,t)=>{p=setTimeout(()=>(c.onTimeout(),t(`${a} URL: ${h.url} exceeds the timeout ${l} ms`)),l)}):null;return(y?Promise.race([y,f]).then(e=>(clearTimeout(p),e)):f).then(e=>c.onResponse(e))})(h,a))),a}function API(e="untitled",t=!1){const{isQX:s,isLoon:o,isSurge:n,isNode:i,isJSBox:r,isScriptable:u}=ENV();return new class{constructor(e,t){this.name=e,this.debug=t,this.http=HTTP(),this.env=ENV(),this.node=(()=>{if(i){return{fs:require("fs")}}return null})(),this.initCache();Promise.prototype.delay=function(e){return this.then(function(t){return((e,t)=>new Promise(function(s){setTimeout(s.bind(null,t),e)}))(e,t)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||"{}")),(o||n)&&(this.cache=JSON.parse($persistentStore.read(this.name)||"{}")),i){let e="root.json";this.node.fs.existsSync(e)||this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.root={},e=`${this.name}.json`,this.node.fs.existsSync(e)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.cache={})}}persistCache(){const e=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(e,this.name),(o||n)&&$persistentStore.write(e,this.name),i&&(this.node.fs.writeFileSync(`${this.name}.json`,e,{flag:"w"},e=>console.log(e)),this.node.fs.writeFileSync("root.json",JSON.stringify(this.root,null,2),{flag:"w"},e=>console.log(e)))}write(e,t){if(this.log(`SET ${t}`),-1!==t.indexOf("#")){if(t=t.substr(1),n||o)return $persistentStore.write(e,t);if(s)return $prefs.setValueForKey(e,t);i&&(this.root[t]=e)}else this.cache[t]=e;this.persistCache()}read(e){return this.log(`READ ${e}`),-1===e.indexOf("#")?this.cache[e]:(e=e.substr(1),n||o?$persistentStore.read(e):s?$prefs.valueForKey(e):i?this.root[e]:void 0)}delete(e){if(this.log(`DELETE ${e}`),-1!==e.indexOf("#")){if(e=e.substr(1),n||o)return $persistentStore.write(null,e);if(s)return $prefs.removeValueForKey(e);i&&delete this.root[e]}else delete this.cache[e];this.persistCache()}notify(e,t="",a="",h={}){const d=h["open-url"],l=h["media-url"];if(s&&$notify(e,t,a,h),n&&$notification.post(e,t,a+`${l?"\n多媒体:"+l:""}`,{url:d}),o){let s={};d&&(s.openUrl=d),l&&(s.mediaUrl=l),"{}"===JSON.stringify(s)?$notification.post(e,t,a):$notification.post(e,t,a,s)}if(i||u){const s=a+(d?`\n点击跳转: ${d}`:"")+(l?`\n多媒体: ${l}`:"");if(r){require("push").schedule({title:e,body:(t?t+"\n":"")+s})}else console.log(`${e}\n${t}\n${s}\n\n`)}}log(e){this.debug&&console.log(`[${this.name}] LOG: ${this.stringify(e)}`)}info(e){console.log(`[${this.name}] INFO: ${this.stringify(e)}`)}error(e){console.log(`[${this.name}] ERROR: ${this.stringify(e)}`)}wait(e){return new Promise(t=>setTimeout(t,e))}done(e={}){s||o||n?$done(e):i&&!r&&"undefined"!=typeof $context&&($context.headers=e.headers,$context.statusCode=e.statusCode,$context.body=e.body)}stringify(e){if("string"==typeof e||e instanceof String)return e;try{return JSON.stringify(e,null,2)}catch(e){return"[object Object]"}}}(e,t)}
|
||||
|
||||
Reference in New Issue
Block a user