Skip to content

Commit baccae7

Browse files
committed
feat: 脚本操作可通过 context.process 按 customName 控制后续操作(前端 >= 2.17.16)
1 parent 6682ab8 commit baccae7

4 files changed

Lines changed: 241 additions & 1 deletion

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.23.20",
3+
"version": "2.23.21",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"packageManager": "pnpm@11.0.9",

backend/src/core/proxy-utils/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ function parse(raw) {
109109
});
110110
}
111111

112+
function shouldSkipByContext(item, context) {
113+
const control = context.process;
114+
if (!control || typeof control !== 'object') return false;
115+
116+
const { type, customNames } = control;
117+
if (!Array.isArray(customNames)) return false;
118+
119+
const matched =
120+
item.customName != null && customNames.includes(item.customName);
121+
122+
if (type === 'disable') return matched;
123+
if (type === 'enable') return !matched;
124+
return false;
125+
}
126+
112127
async function processFn(
113128
proxies,
114129
operators = [],
@@ -128,6 +143,14 @@ async function processFn(
128143
);
129144
continue;
130145
}
146+
if (shouldSkipByContext(item, context)) {
147+
$.info(
148+
`Skipping context-controlled operator: "${
149+
item.type
150+
}" with customName "${item.customName || ''}"`,
151+
);
152+
continue;
153+
}
131154
if (item.disabled) {
132155
$.log(
133156
`Skipping disabled operator: "${
@@ -184,6 +207,14 @@ async function processResponseFn(
184207
let output = normalizeResponse(response);
185208
for (const item of operators) {
186209
if (!isResponseTransformerType(item.type)) continue;
210+
if (shouldSkipByContext(item, context)) {
211+
$.info(
212+
`Skipping context-controlled response transformer: "${
213+
item.type
214+
}" with customName "${item.customName || ''}"`,
215+
);
216+
continue;
217+
}
187218
if (item.disabled) {
188219
$.log(
189220
`Skipping disabled response transformer: "${
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { ProxyUtils } from '@/core/proxy-utils';
5+
6+
describe('Process context control', function () {
7+
it('disables later actions by customName from shared context', async function () {
8+
const operators = [
9+
{
10+
type: 'Script Operator',
11+
args: {
12+
mode: 'script',
13+
content: `function operator(proxies, targetPlatform, context) {
14+
context.process = {
15+
type: 'disable',
16+
customNames: ['branch-b'],
17+
};
18+
return proxies;
19+
}`,
20+
},
21+
},
22+
{
23+
type: 'Script Operator',
24+
customName: 'branch-b',
25+
args: {
26+
mode: 'script',
27+
content: `function operator(proxies) {
28+
return proxies.map((proxy) => ({
29+
...proxy,
30+
name: 'B-' + proxy.name,
31+
}));
32+
}`,
33+
},
34+
},
35+
{
36+
type: 'Script Operator',
37+
customName: 'branch-c',
38+
args: {
39+
mode: 'script',
40+
content: `function operator(proxies) {
41+
return proxies.map((proxy) => ({
42+
...proxy,
43+
name: proxy.name + '-C',
44+
}));
45+
}`,
46+
},
47+
},
48+
];
49+
50+
const output = await ProxyUtils.process([{ name: 'A' }], operators);
51+
52+
expect(output.map((proxy) => proxy.name)).to.deep.equal(['A-C']);
53+
});
54+
55+
it('enables only listed later actions by customName from shared context', async function () {
56+
const operators = [
57+
{
58+
type: 'Script Operator',
59+
args: {
60+
mode: 'script',
61+
content: `function operator(proxies, targetPlatform, context) {
62+
context.process = {
63+
type: 'enable',
64+
customNames: ['branch-c'],
65+
};
66+
return proxies;
67+
}`,
68+
},
69+
},
70+
{
71+
type: 'Script Operator',
72+
customName: 'branch-b',
73+
args: {
74+
mode: 'script',
75+
content: `function operator(proxies) {
76+
return proxies.map((proxy) => ({
77+
...proxy,
78+
name: 'B-' + proxy.name,
79+
}));
80+
}`,
81+
},
82+
},
83+
{
84+
type: 'Script Operator',
85+
customName: 'branch-c',
86+
args: {
87+
mode: 'script',
88+
content: `function operator(proxies) {
89+
return proxies.map((proxy) => ({
90+
...proxy,
91+
name: proxy.name + '-C',
92+
}));
93+
}`,
94+
},
95+
},
96+
];
97+
98+
const output = await ProxyUtils.process([{ name: 'A' }], operators);
99+
100+
expect(output.map((proxy) => proxy.name)).to.deep.equal(['A-C']);
101+
});
102+
103+
it('disables later response transformers by customName from shared context', async function () {
104+
const operators = [
105+
{
106+
type: 'Response Transformer',
107+
args: {
108+
mode: 'script',
109+
content: `function transformFunction(res, context) {
110+
context.process = {
111+
type: 'disable',
112+
customNames: ['branch-b'],
113+
};
114+
res.body += 'A';
115+
return res;
116+
}`,
117+
},
118+
},
119+
{
120+
type: 'Response Transformer',
121+
customName: 'branch-b',
122+
args: {
123+
mode: 'script',
124+
content: `function transformFunction(res) {
125+
res.body += 'B';
126+
return res;
127+
}`,
128+
},
129+
},
130+
{
131+
type: 'Response Transformer',
132+
customName: 'branch-c',
133+
args: {
134+
mode: 'script',
135+
content: `function transformFunction(res) {
136+
res.body += 'C';
137+
return res;
138+
}`,
139+
},
140+
},
141+
];
142+
143+
const output = await ProxyUtils.processResponse({ body: '' }, operators);
144+
145+
expect(output.body).to.equal('AC');
146+
});
147+
148+
it('enables only listed later response transformers by customName from shared context', async function () {
149+
const operators = [
150+
{
151+
type: 'Response Transformer',
152+
args: {
153+
mode: 'script',
154+
content: `function transformFunction(res, context) {
155+
context.process = {
156+
type: 'enable',
157+
customNames: ['branch-c'],
158+
};
159+
res.body += 'A';
160+
return res;
161+
}`,
162+
},
163+
},
164+
{
165+
type: 'Response Transformer',
166+
customName: 'branch-b',
167+
args: {
168+
mode: 'script',
169+
content: `function transformFunction(res) {
170+
res.body += 'B';
171+
return res;
172+
}`,
173+
},
174+
},
175+
{
176+
type: 'Response Transformer',
177+
customName: 'branch-c',
178+
args: {
179+
mode: 'script',
180+
content: `function transformFunction(res) {
181+
res.body += 'C';
182+
return res;
183+
}`,
184+
},
185+
},
186+
];
187+
188+
const output = await ProxyUtils.processResponse({ body: '' }, operators);
189+
190+
expect(output.body).to.equal('AC');
191+
});
192+
});

scripts/demo.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,23 @@ function operator(proxies = [], targetPlatform, context) {
319319

320320
// context 为传入的上下文, 可在多个脚本中共享使用
321321
// 其中 env 为 环境信息, 包含运行版本和其他后端信息
322+
// 可通过 context.process 动态控制本次处理中后续 action 是否执行
323+
// 只按 customName 匹配, 只影响当前脚本之后的 action
324+
// type 为 disable 时, 跳过 customNames 中的后续 action
325+
// type 为 enable 时, 只执行 customNames 中的后续 action
326+
// 示例: 准备 A/B/C 三个脚本操作, A 根据请求 UA 决定后续使用 B 还是 C
327+
// if ($options?._req) {
328+
// const { headers, url, path } = $options?._req || {};
329+
// // 获取 user-agent
330+
// const ua = headers?.["user-agent"] || headers?.["User-Agent"];
331+
// console.log(`User-Agent: ${ua}`);
332+
// context.process = /Surge/i.test(ua)
333+
// ? { type: 'enable', customNames: ['branch-b'] }
334+
// : { type: 'enable', customNames: ['branch-c'] };
335+
// }
336+
// 脚本操作和修改响应(Response Transformer)各自有独立的 context.process 控制, 两者不互相跨
337+
// 脚本操作只控制后续非修改响应操作
338+
// 修改响应中设置 context.process 只控制后续修改响应操作
322339

323340
// 其中 source 为 订阅和组合订阅的数据, 有三种情况, 按需判断 (若只需要取订阅/组合订阅名称 直接用 `_subName` `_subDisplayName` `_collectionName` `_collectionDisplayName` 即可)
324341

0 commit comments

Comments
 (0)