Skip to content

Commit 24f1aa8

Browse files
committed
feat: 文件路由支持使用 includeUnsupportedProxy 参数开启 含不支持的协议; 件生成 mihomo 配置时, 支持 含不支持的协议 选项
1 parent a09ae7c commit 24f1aa8

4 files changed

Lines changed: 260 additions & 3 deletions

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.27.0",
3+
"version": "2.28.0",
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/processors/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ function ScriptOperator(
426426
produceType: 'internal',
427427
produceOpts: {
428428
'delete-underscore-fields': true,
429+
'include-unsupported-proxy':
430+
output?.$file
431+
?.includeUnsupportedProxy,
429432
},
430433
}),
431434
}),
@@ -474,7 +477,9 @@ function ScriptOperator(
474477
platform: 'mihomo',
475478
produceType: 'internal',
476479
produceOpts: {
477-
'delete-underscore-fields': true
480+
'delete-underscore-fields': true,
481+
'include-unsupported-proxy':
482+
$file.includeUnsupportedProxy,
478483
}
479484
}),
480485
})))

backend/src/restful/file.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ async function getFile(req, res, next) {
6565
content,
6666
mergeSources,
6767
ignoreFailedRemoteFile,
68+
includeUnsupportedProxy,
6869
proxy,
6970
noCache,
7071
produceType,
@@ -203,6 +204,9 @@ async function getFile(req, res, next) {
203204
if (produceType) {
204205
$.info(`指定生产类型: ${produceType}`);
205206
}
207+
if (includeUnsupportedProxy) {
208+
$.info(`包含官方/商店版不支持的协议: ${includeUnsupportedProxy}`);
209+
}
206210

207211
const allFiles = $.read(FILES_KEY);
208212
const fakeFile = {
@@ -213,6 +217,14 @@ async function getFile(req, res, next) {
213217
const file = _fakeFile ? fakeFile : findByName(allFiles, name);
214218
if (file) {
215219
try {
220+
const sourceFile = includeUnsupportedProxy
221+
? {
222+
...file,
223+
includeUnsupportedProxy,
224+
}
225+
: _fakeFile
226+
? fakeFile
227+
: undefined;
216228
const output = await produceArtifact({
217229
type: 'file',
218230
name,
@@ -226,7 +238,7 @@ async function getFile(req, res, next) {
226238
noCache,
227239
produceType,
228240
all: true,
229-
file: _fakeFile ? fakeFile : undefined,
241+
file: sourceFile,
230242
});
231243

232244
try {
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { expect } from 'chai';
2+
import { after, before, beforeEach, describe, it } from 'mocha';
3+
4+
import { FILES_KEY, SUBS_KEY } from '@/constants';
5+
6+
let $;
7+
let registerFileRoutes;
8+
let originalError;
9+
let originalInfo;
10+
let originalNotify;
11+
let originalRead;
12+
let originalWarn;
13+
let originalWrite;
14+
let state;
15+
16+
function createRouteApp() {
17+
const handlers = new Map();
18+
const methods = ['get', 'post', 'put', 'patch', 'delete'];
19+
20+
const app = {
21+
handlers,
22+
route(pattern) {
23+
const chain = {};
24+
methods.forEach((method) => {
25+
chain[method] = (handler) => {
26+
handlers.set(`${method.toUpperCase()} ${pattern}`, handler);
27+
return chain;
28+
};
29+
});
30+
return chain;
31+
},
32+
};
33+
34+
methods.forEach((method) => {
35+
app[method] = (pattern, handler) => {
36+
handlers.set(`${method.toUpperCase()} ${pattern}`, handler);
37+
return app;
38+
};
39+
});
40+
41+
return app;
42+
}
43+
44+
function getHandler(pattern) {
45+
const app = createRouteApp();
46+
registerFileRoutes(app);
47+
return app.handlers.get(`GET ${pattern}`);
48+
}
49+
50+
function createResponse(routePath) {
51+
return {
52+
headers: {},
53+
req: {
54+
route: {
55+
path: routePath,
56+
},
57+
},
58+
sent: null,
59+
statusCode: 200,
60+
removeHeader(key) {
61+
delete this.headers[key];
62+
return this;
63+
},
64+
send(payload) {
65+
this.sent = payload;
66+
return this;
67+
},
68+
json(payload) {
69+
this.sent = payload;
70+
return this;
71+
},
72+
set(key, value) {
73+
this.headers[key] = value;
74+
return this;
75+
},
76+
status(code) {
77+
this.statusCode = code;
78+
return this;
79+
},
80+
};
81+
}
82+
83+
async function requestFile(name, query = {}) {
84+
const handler = getHandler('/api/file/:name');
85+
const res = createResponse('/api/file/:name');
86+
87+
await handler(
88+
{
89+
body: {},
90+
headers: {},
91+
method: 'GET',
92+
params: { name },
93+
path: `/api/file/${name}`,
94+
query,
95+
socket: {},
96+
url: `/api/file/${name}`,
97+
},
98+
res,
99+
);
100+
101+
return res;
102+
}
103+
104+
describe('mihomo profile file routes', function () {
105+
before(async function () {
106+
({ default: $ } = require('@/core/app'));
107+
({ default: registerFileRoutes } = require('@/restful/file'));
108+
109+
originalRead = $.read.bind($);
110+
originalWrite = $.write.bind($);
111+
originalInfo = $.info.bind($);
112+
originalWarn = $.warn.bind($);
113+
originalError = $.error.bind($);
114+
originalNotify = $.notify.bind($);
115+
});
116+
117+
after(function () {
118+
if ($) {
119+
$.read = originalRead;
120+
$.write = originalWrite;
121+
$.info = originalInfo;
122+
$.warn = originalWarn;
123+
$.error = originalError;
124+
$.notify = originalNotify;
125+
}
126+
});
127+
128+
beforeEach(function () {
129+
state = {
130+
[SUBS_KEY]: [
131+
{
132+
name: 'demo-sub',
133+
source: 'local',
134+
content:
135+
'proxies:\n' +
136+
' - {name: Supported, type: ss, server: ss.example.com, port: 8388, cipher: aes-128-gcm, password: secret}\n' +
137+
' - {name: Unsupported, type: naive, server: naive.example.com, port: 443, username: user, password: pass, sni: naive.example.com}',
138+
process: [],
139+
},
140+
],
141+
[FILES_KEY]: [],
142+
};
143+
144+
$.read = (key) => state[key] || [];
145+
$.write = (data, key) => {
146+
state[key] = data;
147+
return true;
148+
};
149+
$.info = () => {};
150+
$.warn = () => {};
151+
$.error = () => {};
152+
$.notify = () => {};
153+
});
154+
155+
it('keeps unsupported proxies for YAML mihomoProfile overrides when enabled', async function () {
156+
state[FILES_KEY] = [
157+
{
158+
name: 'yaml-file',
159+
type: 'mihomoProfile',
160+
sourceType: 'subscription',
161+
sourceName: 'demo-sub',
162+
includeUnsupportedProxy: true,
163+
process: [
164+
{
165+
type: 'Script Operator',
166+
args: {
167+
mode: 'script',
168+
content: 'mixed-port: 7890',
169+
},
170+
},
171+
],
172+
},
173+
];
174+
175+
const res = await requestFile('yaml-file');
176+
177+
expect(res.statusCode).to.equal(200);
178+
expect(res.sent).to.include('mixed-port: 7890');
179+
expect(res.sent).to.include('name: Supported');
180+
expect(res.sent).to.include('name: Unsupported');
181+
});
182+
183+
it('filters unsupported proxies for JS mihomoProfile overrides when disabled', async function () {
184+
state[FILES_KEY] = [
185+
{
186+
name: 'js-file',
187+
type: 'mihomoProfile',
188+
sourceType: 'subscription',
189+
sourceName: 'demo-sub',
190+
includeUnsupportedProxy: false,
191+
process: [
192+
{
193+
type: 'Script Operator',
194+
args: {
195+
mode: 'script',
196+
content:
197+
'async function main(config) { config["mixed-port"] = 7891; return config; }',
198+
},
199+
},
200+
],
201+
},
202+
];
203+
204+
const res = await requestFile('js-file');
205+
206+
expect(res.statusCode).to.equal(200);
207+
expect(res.sent).to.include('mixed-port: 7891');
208+
expect(res.sent).to.include('name: Supported');
209+
expect(res.sent).to.not.include('name: Unsupported');
210+
});
211+
212+
it('query includeUnsupportedProxy overrides stored false for mihomoProfile files', async function () {
213+
state[FILES_KEY] = [
214+
{
215+
name: 'query-true-file',
216+
type: 'mihomoProfile',
217+
sourceType: 'subscription',
218+
sourceName: 'demo-sub',
219+
includeUnsupportedProxy: false,
220+
process: [
221+
{
222+
type: 'Script Operator',
223+
args: {
224+
mode: 'script',
225+
content: 'mixed-port: 7892',
226+
},
227+
},
228+
],
229+
},
230+
];
231+
232+
const res = await requestFile('query-true-file', {
233+
includeUnsupportedProxy: 'true',
234+
});
235+
236+
expect(res.statusCode).to.equal(200);
237+
expect(res.sent).to.include('mixed-port: 7892');
238+
expect(res.sent).to.include('name: Unsupported');
239+
});
240+
});

0 commit comments

Comments
 (0)