diff --git a/backend/package.json b/backend/package.json index 36de3af..4f03d36 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.372", + "version": "2.14.373", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/index.js b/backend/src/core/proxy-utils/index.js index eb6d212..15213e7 100644 --- a/backend/src/core/proxy-utils/index.js +++ b/backend/src/core/proxy-utils/index.js @@ -77,7 +77,13 @@ function parse(raw) { return proxies; } -async function processFn(proxies, operators = [], targetPlatform, source) { +async function processFn( + proxies, + operators = [], + targetPlatform, + source, + $options, +) { for (const item of operators) { // process script let script; @@ -176,6 +182,7 @@ async function processFn(proxies, operators = [], targetPlatform, source) { targetPlatform, $arguments, source, + $options, ); } else { processor = PROXY_PROCESSORS[item.type](item.args || {}); diff --git a/backend/src/core/proxy-utils/processors/index.js b/backend/src/core/proxy-utils/processors/index.js index 4e611ef..111cce2 100644 --- a/backend/src/core/proxy-utils/processors/index.js +++ b/backend/src/core/proxy-utils/processors/index.js @@ -316,7 +316,7 @@ function RegexDeleteOperator(regex) { 1. This function name should be `operator`! 2. Always declare variables before using them! */ -function ScriptOperator(script, targetPlatform, $arguments, source) { +function ScriptOperator(script, targetPlatform, $arguments, source, $options) { return { name: 'Script Operator', func: async (proxies) => { @@ -326,6 +326,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source) { 'operator', script, $arguments, + $options, ); output = operator(proxies, targetPlatform, { source, ...env }); })(); @@ -338,9 +339,9 @@ function ScriptOperator(script, targetPlatform, $arguments, source) { 'operator', `async function operator(input = []) { if (input && (input.$files || input.$content)) { - let { $content, $files } = input + let { $content, $files, $options } = input ${script} - return { $content, $files } + return { $content, $files, $options } } else { let proxies = input let list = [] @@ -352,6 +353,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source) { } }`, $arguments, + $options, ); output = operator(proxies, targetPlatform, { source, ...env }); })(); @@ -794,7 +796,7 @@ function TypeFilter(types) { 1. This function name should be `filter`! 2. Always declare variables before using them! */ -function ScriptFilter(script, targetPlatform, $arguments, source) { +function ScriptFilter(script, targetPlatform, $arguments, source, $options) { return { name: 'Script Filter', func: async (proxies) => { @@ -804,6 +806,7 @@ function ScriptFilter(script, targetPlatform, $arguments, source) { 'filter', script, $arguments, + $options, ); output = filter(proxies, targetPlatform, { source, ...env }); })(); @@ -826,6 +829,7 @@ function ScriptFilter(script, targetPlatform, $arguments, source) { return list }`, $arguments, + $options, ); output = filter(proxies, targetPlatform, { source, ...env }); })(); @@ -966,7 +970,7 @@ function clone(object) { return JSON.parse(JSON.stringify(object)); } -function createDynamicFunction(name, script, $arguments) { +function createDynamicFunction(name, script, $arguments, $options) { const flowUtils = { getFlowField, getFlowHeaders, @@ -978,6 +982,7 @@ function createDynamicFunction(name, script, $arguments) { if ($.env.isLoon) { return new Function( '$arguments', + '$options', '$substore', 'lodash', '$persistentStore', @@ -991,6 +996,7 @@ function createDynamicFunction(name, script, $arguments) { `${script}\n return ${name}`, )( $arguments, + $options, $, lodash, // eslint-disable-next-line no-undef @@ -1008,6 +1014,7 @@ function createDynamicFunction(name, script, $arguments) { } else { return new Function( '$arguments', + '$options', '$substore', 'lodash', 'ProxyUtils', @@ -1018,6 +1025,7 @@ function createDynamicFunction(name, script, $arguments) { `${script}\n return ${name}`, )( $arguments, + $options, $, lodash, ProxyUtils, diff --git a/backend/src/restful/download.js b/backend/src/restful/download.js index a7bd067..4af2461 100644 --- a/backend/src/restful/download.js +++ b/backend/src/restful/download.js @@ -70,6 +70,24 @@ async function downloadSubscription(req, res) { includeUnsupportedProxy, resultFormat, } = req.query; + let $options = {}; + if (req.query.$options) { + try { + // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` + $options = JSON.parse(decodeURIComponent(req.query.$options)); + } catch (e) { + for (const pair of req.query.$options.split('&')) { + const key = pair.split('=')[0]; + const value = pair.split('=')[1]; + // 部分兼容之前的逻辑 const value = pair.split('=')[1] || true; + $options[key] = + value == null || value === '' + ? true + : decodeURIComponent(value); + } + } + $.info(`传入 $options: ${JSON.stringify($options)}`); + } if (url) { url = decodeURIComponent(url); $.info(`指定远程订阅 URL: ${url}`); @@ -116,6 +134,7 @@ async function downloadSubscription(req, res) { produceOpts: { 'include-unsupported-proxy': includeUnsupportedProxy, }, + $options, }); if ( @@ -247,6 +266,25 @@ async function downloadCollection(req, res) { resultFormat, } = req.query; + let $options = {}; + if (req.query.$options) { + try { + // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` + $options = JSON.parse(decodeURIComponent(req.query.$options)); + } catch (e) { + for (const pair of req.query.$options.split('&')) { + const key = pair.split('=')[0]; + const value = pair.split('=')[1]; + // 部分兼容之前的逻辑 const value = pair.split('=')[1] || true; + $options[key] = + value == null || value === '' + ? true + : decodeURIComponent(value); + } + } + $.info(`传入 $options: ${JSON.stringify($options)}`); + } + if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub); $.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`); @@ -272,6 +310,7 @@ async function downloadCollection(req, res) { produceOpts: { 'include-unsupported-proxy': includeUnsupportedProxy, }, + $options, }); // forward flow header from the first subscription in this collection diff --git a/backend/src/restful/file.js b/backend/src/restful/file.js index db865b4..13dd404 100644 --- a/backend/src/restful/file.js +++ b/backend/src/restful/file.js @@ -60,6 +60,24 @@ async function getFile(req, res) { mergeSources, ignoreFailedRemoteFile, } = req.query; + let $options = {}; + if (req.query.$options) { + try { + // 支持 `#${encodeURIComponent(JSON.stringify({arg1: "1"}))}` + $options = JSON.parse(decodeURIComponent(req.query.$options)); + } catch (e) { + for (const pair of req.query.$options.split('&')) { + const key = pair.split('=')[0]; + const value = pair.split('=')[1]; + // 部分兼容之前的逻辑 const value = pair.split('=')[1] || true; + $options[key] = + value == null || value === '' + ? true + : decodeURIComponent(value); + } + } + $.info(`传入 $options: ${JSON.stringify($options)}`); + } if (url) { url = decodeURIComponent(url); $.info(`指定远程文件 URL: ${url}`); @@ -101,6 +119,7 @@ async function getFile(req, res) { content, mergeSources, ignoreFailedRemoteFile, + $options, }); try { diff --git a/backend/src/restful/sync.js b/backend/src/restful/sync.js index 61027fa..536f7b8 100644 --- a/backend/src/restful/sync.js +++ b/backend/src/restful/sync.js @@ -37,6 +37,7 @@ async function produceArtifact({ produceOpts = {}, subscription, awaitCustomCache, + $options, }) { platform = platform || 'JSON'; @@ -158,6 +159,7 @@ async function produceArtifact({ sub.process || [], platform, { [sub.name]: sub }, + $options, ); if (proxies.length === 0) { throw new Error(`订阅 ${name} 中不含有效节点`); @@ -259,7 +261,11 @@ async function produceArtifact({ currentProxies, sub.process || [], platform, - { [sub.name]: sub, _collection: collection }, + { + [sub.name]: sub, + _collection: collection, + $options, + }, ); results[name] = currentProxies; processed++; @@ -312,6 +318,7 @@ async function produceArtifact({ collection.process || [], platform, { _collection: collection }, + $options, ); if (proxies.length === 0) { throw new Error(`组合订阅 ${name} 中不含有效节点`); @@ -460,10 +467,10 @@ async function produceArtifact({ const processed = Array.isArray(file.process) && file.process.length > 0 ? await ProxyUtils.process( - { $files: files, $content: filesContent }, + { $files: files, $content: filesContent, $options }, file.process, ) - : { $content: filesContent, $files: files }; + : { $content: filesContent, $files: files, $options }; return processed?.$content ?? ''; } diff --git a/scripts/demo.js b/scripts/demo.js index 3347242..aaba6db 100644 --- a/scripts/demo.js +++ b/scripts/demo.js @@ -23,6 +23,17 @@ function operator(proxies = [], targetPlatform, context) { // $arguments 为传入的脚本参数 + // $options 为通过链接传入的参数 + // 例如: { arg1: 'a', arg2: 'b' } + // 可这样传: + // 先这样处理 encodeURIComponent(JSON.stringify({ arg1: 'a', arg2: 'b' })) + // /api/file/foo?$options=%7B%22arg1%22%3A%22a%22%2C%22arg2%22%3A%22b%22%7D + // 或这样传: + // 先这样处理 encodeURIComponent('arg1=a&arg2=b') + // /api/file/foo?$options=arg1%3Da%26arg2%3Db + + // console.log($options) + // targetPlatform 为输出的目标平台 // lodash @@ -133,7 +144,7 @@ function operator(proxies = [], targetPlatform, context) { // yaml.proxies.unshift(...clashMetaProxies) // $content = ProxyUtils.yaml.dump(yaml) - // { $content, $files } will be passed to the next operator + // { $content, $files, $options } will be passed to the next operator // $content is the final content of the file // flowUtils 为机场订阅流量信息处理工具 @@ -141,7 +152,7 @@ function operator(proxies = [], targetPlatform, context) { // 1. https://t.me/zhetengsha/948 // context 为传入的上下文 - // 有三种情况, 按需判断 + // 其中 source 为 订阅和组合订阅的数据, 有三种情况, 按需判断 // 若存在 `source._collection` 且 `source._collection.subscriptions` 中的 key 在 `source` 上也存在, 说明输出结果为组合订阅, 但是脚本设置在单条订阅上