Compare commits

...

13 Commits

24 changed files with 389 additions and 43 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.192",
"version": "2.14.201",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@@ -1,4 +1,4 @@
import YAML from 'static-js-yaml';
import YAML from '@/utils/yaml';
import download from '@/utils/download';
import { isIPv4, isIPv6, isValidPortNumber } from '@/utils';
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
@@ -266,9 +266,13 @@ function lastParse(proxy) {
}
if (proxy.network === 'h2') {
const host = proxy['h2-opts']?.headers?.host;
const path = proxy['h2-opts']?.path;
if (host && !Array.isArray(host)) {
proxy['h2-opts'].headers.host = [host];
}
if (Array.isArray(path)) {
proxy['h2-opts'].path = path[0];
}
}
if (proxy.tls && !proxy.sni) {
if (proxy.network) {
@@ -313,6 +317,17 @@ function lastParse(proxy) {
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) {
delete proxy.ports;
}
if (['vless'].includes(proxy.type)) {
if (['http'].includes(proxy.network)) {
let transportPath = proxy[`${proxy.network}-opts`]?.path;
if (!transportPath) {
if (!proxy[`${proxy.network}-opts`]) {
proxy[`${proxy.network}-opts`] = {};
}
proxy[`${proxy.network}-opts`].path = ['/'];
}
}
}
return proxy;
}

View File

@@ -45,7 +45,7 @@ function URI_SS() {
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
let v2rayPlugin = parsed[2];
if (v2rayPlugin) {
proxy.obfs = 'v2ray-plugin';
proxy.plugin = 'v2ray-plugin';
proxy['plugin-opts'] = JSON.parse(
Base64.decode(v2rayPlugin),
);
@@ -89,7 +89,7 @@ function URI_SS() {
};
break;
case 'v2ray-plugin':
proxy.obfs = 'v2ray-plugin';
proxy.plugin = 'v2ray-plugin';
proxy['plugin-opts'] = {
mode: 'websocket',
host: getIfNotBlank(params['obfs-host']),

View File

@@ -117,7 +117,7 @@ port = digits:[0-9]+ {
method = comma cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
username = & {
let j = peg$currPos;

View File

@@ -115,7 +115,7 @@ port = digits:[0-9]+ {
method = comma cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"auto"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"none"/"rc4-md5"/"rc4"/"salsa20"/"xchacha20-ietf-poly1305");
username = & {
let j = peg$currPos;

View File

@@ -149,7 +149,7 @@ uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim();
method = comma "method" equals cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }

View File

@@ -147,7 +147,7 @@ uuid = comma "password" equals uuid:[^=,]+ { proxy.uuid = uuid.join("").trim();
method = comma "method" equals cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"none"/"rc2-cfb"/"rc4-md5-6"/"rc4-md5"/"salsa20"/"xchacha20-ietf-poly1305");
aead = comma "aead" equals flag:bool { proxy.aead = flag; }
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }

View File

@@ -188,7 +188,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -186,7 +186,7 @@ vmess_aead = comma "vmess-aead" equals flag:bool { proxy.aead = flag; }
method = comma "encrypt-method" equals cipher:cipher {
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");
cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305");
ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
ws_headers = comma "ws-headers" equals headers:$[^,]+ {

View File

@@ -1,4 +1,4 @@
import { safeLoad } from 'static-js-yaml';
import { safeLoad } from '@/utils/yaml';
import { Base64 } from 'js-base64';
function HTML() {

View File

@@ -14,6 +14,7 @@ import {
getFlowField,
getFlowHeaders,
parseFlowHeaders,
validCheck,
flowTransfer,
} from '@/utils/flow';
@@ -806,6 +807,7 @@ function createDynamicFunction(name, script, $arguments) {
getFlowHeaders,
parseFlowHeaders,
flowTransfer,
validCheck,
};
if ($.env.isLoon) {
return new Function(

View File

@@ -144,6 +144,10 @@ export default function Clash_Producer() {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}
delete proxy.subName;
delete proxy.collectionName;
if (

View File

@@ -160,6 +160,9 @@ export default function ClashMeta_Producer() {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}
delete proxy.subName;
delete proxy.collectionName;
if (

View File

@@ -32,6 +32,32 @@ export default function Loon_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
if (
![
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
);

View File

@@ -37,7 +37,36 @@ function shadowsocks(proxy) {
const result = new Result(proxy);
const append = result.append.bind(result);
const appendIfPresent = result.appendIfPresent.bind(result);
if (!proxy.cipher) {
proxy.cipher = 'none';
}
if (
![
'none',
'rc4-md5',
'rc4-md5-6',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'cast5-cfb',
'des-cfb',
'rc2-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
append(`shadowsocks=${proxy.server}:${proxy.port}`);
append(`,method=${proxy.cipher}`);
append(`,password=${proxy.password}`);

View File

@@ -163,6 +163,9 @@ export default function ShadowRocket_Producer() {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}
delete proxy.subName;
delete proxy.collectionName;
if (

View File

@@ -242,6 +242,9 @@ export default function Stash_Producer() {
proxy.fingerprint = proxy['tls-fingerprint'];
}
delete proxy['tls-fingerprint'];
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
delete proxy.tls;
}
if (proxy['test-url']) {
proxy['benchmark-url'] = proxy['test-url'];

View File

@@ -31,6 +31,32 @@ export default function Surfboard_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (
![
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(`,encrypt-method=${proxy.cipher}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

View File

@@ -44,6 +44,41 @@ export default function Surge_Producer() {
function shadowsocks(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (!proxy.cipher) {
proxy.cipher = 'none';
}
if (
![
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305',
'xchacha20-ietf-poly1305',
'rc4',
'rc4-md5',
'aes-128-cfb',
'aes-192-cfb',
'aes-256-cfb',
'aes-128-ctr',
'aes-192-ctr',
'aes-256-ctr',
'bf-cfb',
'camellia-128-cfb',
'camellia-192-cfb',
'camellia-256-cfb',
'cast5-cfb',
'des-cfb',
'idea-cfb',
'rc2-cfb',
'seed-cfb',
'salsa20',
'chacha20',
'chacha20-ietf',
'none',
].includes(proxy.cipher)
) {
throw new Error(`cipher ${proxy.cipher} is not supported`);
}
result.append(`,encrypt-method=${proxy.cipher}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

View File

@@ -1,4 +1,4 @@
import YAML from 'static-js-yaml';
import YAML from '@/utils/yaml';
function QXFilter() {
const type = 'SINGLE';

View File

@@ -4,7 +4,12 @@ import { HTTP, ENV } from '@/vendor/open-api';
import { hex_md5 } from '@/vendor/md5';
import resourceCache from '@/utils/resource-cache';
import headersResourceCache from '@/utils/headers-resource-cache';
import { getFlowField } from '@/utils/flow';
import {
getFlowField,
getFlowHeaders,
parseFlowHeaders,
validCheck,
} from '@/utils/flow';
import $ from '@/core/app';
const tasks = new Map();
@@ -64,36 +69,40 @@ export default async function download(rawUrl, ua, timeout) {
timeout: requestTimeout,
});
const result = new Promise((resolve, reject) => {
// try to find in app cache
const cached = resourceCache.get(id);
if (!$arguments?.noCache && cached) {
resolve(cached);
} else {
$.info(
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nURL: ${url}`,
);
http.get(url)
.then((resp) => {
const { body, headers } = resp;
if (headers) {
const flowInfo = getFlowField(headers);
if (flowInfo) {
headersResourceCache.set(url, flowInfo);
}
}
if (body.replace(/\s/g, '').length === 0)
reject(new Error('远程资源内容为空!'));
else {
resourceCache.set(id, body);
resolve(body);
}
})
.catch(() => {
reject(new Error(`无法下载 URL${url}`));
});
let result;
// try to find in app cache
const cached = resourceCache.get(id);
if (!$arguments?.noCache && cached) {
result = cached;
} else {
$.info(
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nURL: ${url}`,
);
try {
const { body, headers } = await http.get(url);
if (headers) {
const flowInfo = getFlowField(headers);
if (flowInfo) {
headersResourceCache.set(url, flowInfo);
}
}
if (body.replace(/\s/g, '').length === 0)
throw new Error(new Error('远程资源内容为空'));
resourceCache.set(id, body);
result = body;
} catch (e) {
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
}
});
}
// 检查订阅有效性
if ($arguments?.validCheck) {
await validCheck(parseFlowHeaders(await getFlowHeaders(url)));
}
if (!isNode) {
tasks.set(id, result);

View File

@@ -120,3 +120,26 @@ export function flowTransfer(flow, unit = 'B') {
? { value: flow.toFixed(1), unit: unit }
: flowTransfer(flow / 1024, unitList[++unitIndex]);
}
export function validCheck(flow) {
if (!flow) {
throw new Error('没有流量信息');
}
if (flow?.expires && flow.expires * 1000 < Date.now()) {
const date = new Date(flow.expires * 1000).toLocaleDateString();
throw new Error(`订阅已过期: ${date}`);
}
if (flow?.total) {
const upload = flow.usage?.upload || 0;
const download = flow.usage?.download || 0;
if (flow.total - upload - download < 0) {
const current = upload + download;
const currT = flowTransfer(Math.abs(current));
currT.value = current < 0 ? '-' + currT.value : currT.value;
const totalT = flowTransfer(flow.total);
throw new Error(
`流量已用完: ${currT.value} ${currT.unit} / ${totalT.value} ${totalT.unit}`,
);
}
}
}

37
backend/src/utils/yaml.js Normal file
View File

@@ -0,0 +1,37 @@
import YAML from 'static-js-yaml';
function retry(fn, content, ...args) {
try {
return fn(content, ...args);
} catch (e) {
return fn(
dump(
fn(
content.replace(/!<str>\s*/g, '__SubStoreJSYAMLString__'),
...args,
),
).replace(/__SubStoreJSYAMLString__/g, ''),
...args,
);
}
}
export function safeLoad(content, ...args) {
return retry(YAML.safeLoad, content, ...args);
}
export function load(content, ...args) {
return retry(YAML.load, content, ...args);
}
export function safeDump(...args) {
return YAML.safeDump(...args);
}
export function dump(...args) {
return YAML.dump(...args);
}
export default {
safeLoad,
load,
safeDump,
dump,
};

131
scripts/demo.js Normal file
View File

@@ -0,0 +1,131 @@
function operator(proxies = [], targetPlatform, context) {
// 支持快捷操作 不一定要写一个 function
// 可参考 https://t.me/zhetengsha/970
// https://t.me/zhetengsha/1009
// proxies 为传入的内部节点数组
// 结构大致参考了 Clash.Meta(mihomo) 有私货
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
// 1. `no-resolve` 为不解析域名
// 2. 域名解析后 会多一个 `resolved` 字段
// 3. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
// $arguments 为传入的脚本参数
// targetPlatform 为输出的目标平台
// lodash
// $substore 为 OpenAPI
// 参考 https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/README.md
// scriptResourceCache 缓存
// 可参考 https://t.me/zhetengsha/1003
// ProxyUtils 为节点处理工具
// 可参考 https://t.me/zhetengsha/1066
// const ProxyUtils = {
// parse, // 订阅解析
// process, // 节点操作/文件操作
// produce, // 输出订阅
// isIPv4,
// isIPv6,
// isIP,
// yaml, // yaml 解析和生成
// }
// flowUtils 为机场订阅流量信息处理工具
// 可参考 https://t.me/zhetengsha/948
// https://github.com/sub-store-org/Sub-Store/blob/31b6dd0507a9286d6ab834ec94ad3050f6bdc86b/backend/src/utils/download.js#L104
// context 为传入的上下文
// 有三种情况, 按需判断
// 若存在 `source._collection` 且 `source._collection.subscriptions` 中的 key 在 `source` 上也存在, 说明输出结果为组合订阅, 但是脚本设置在单条订阅上
// 若存在 `source._collection` 但 `source._collection.subscriptions` 中的 key 在 `source` 上不存在, 说明输出结果为组合订阅, 脚本设置在组合订阅上
// 若不存在 `source._collection`, 说明输出结果为单条订阅, 脚本设置在此单条订阅上
// 1. 输出单条订阅 sub-1 时, 该单条订阅中的脚本上下文为:
// {
// "source": {
// "sub-1": {
// "name": "sub-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": true,
// "process": [],
// "icon": "",
// "source": "local",
// "url": "",
// "content": "",
// "ua": "",
// "display-name": "",
// "useCacheForFailedRemoteSub": false
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 2. 输出组合订阅 collection-1 时, 该组合订阅中的脚本上下文为:
// {
// "source": {
// "_collection": {
// "name": "collection-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": false,
// "icon": "",
// "process": [],
// "subscriptions": [
// "sub-1"
// ],
// "display-name": ""
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 3. 输出组合订阅 collection-1 时, 该组合订阅中的单条订阅 sub-1 中的某个脚本上下文为:
// {
// "source": {
// "sub-1": {
// "name": "sub-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": true,
// "icon": "",
// "process": [],
// "source": "local",
// "url": "",
// "content": "",
// "ua": "",
// "display-name": "",
// "useCacheForFailedRemoteSub": false
// },
// "_collection": {
// "name": "collection-1",
// "displayName": "",
// "mergeSources": "",
// "ignoreFailedRemoteSub": false,
// "icon": "",
// "process": [],
// "subscriptions": [
// "sub-1"
// ],
// "display-name": ""
// }
// },
// "backend": "Node",
// "version": "2.14.198"
// }
// 参数说明
// 可参考 https://github.com/sub-store-org/Sub-Store/wiki/%E9%93%BE%E6%8E%A5%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
console.log(JSON.stringify(context, null, 2))
return proxies
}