Compare commits

..

12 Commits

Author SHA1 Message Date
xream
ca0d800bbb release: backend version 2.14.184 2024-01-19 12:54:40 +08:00
xream
31b48d7a6c Merge pull request #274 from izhangxm/feat_add_proxy_convter_api
增加规则转换与协议转换API接口
2024-01-19 12:35:32 +08:00
makabaka
ab96ae9413 增加规则转换与协议转换API接口 2024-01-19 12:23:04 +08:00
xream
3fc507b576 feat: 解析并删除旧的 ws-path ws-headers 字段 2024-01-19 10:18:27 +08:00
xream
2f2dbbdb68 release: backend version 2.14.182 2024-01-18 17:17:15 +08:00
xream
1543e76841 Merge pull request #273 from izhangxm/master
修复clash规则头部有注释的情况下规则转换功能失败的问题
2024-01-18 17:07:59 +08:00
makabaka
74c4719806 fix_clashprovider_test 2024-01-18 15:09:24 +08:00
xream
b80d7f5875 feat: Clash 节点支持 fingerprint(内部转为 tls-fingerprint); 支持 Clash 配置文件中的 global-client-fingerprint 优先级低于 proxy 内的 client-fingerprint 2024-01-18 12:14:35 +08:00
xream
779950ab11 Revert "fix: sing-box fingerprint"
This reverts commit 42404537e8.
2024-01-18 11:36:07 +08:00
xream
42404537e8 fix: sing-box fingerprint 2024-01-18 11:29:15 +08:00
xream
228566116d feat: 支持同步配置时选择包含官方/商店版不支持的协议; 同步配置优化 2024-01-18 06:18:05 +08:00
xream
9bb06bf438 feat: 兼容不规范的 VLESS URI 2024-01-18 01:17:06 +08:00
10 changed files with 169 additions and 38 deletions

View File

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

View File

@@ -224,6 +224,19 @@ function lastParse(proxy) {
.replace(/^\[/, '')
.replace(/\]$/, '');
}
if (proxy.network === 'ws') {
if (!proxy['ws-opts'] && (proxy['ws-path'] || proxy['ws-headers'])) {
proxy['ws-opts'] = {};
if (proxy['ws-path']) {
proxy['ws-opts'].path = proxy['ws-path'];
}
if (proxy['ws-headers']) {
proxy['ws-opts'].headers = proxy['ws-headers'];
}
}
delete proxy['ws-path'];
delete proxy['ws-headers'];
}
if (proxy.type === 'trojan') {
if (proxy.network === 'tcp') {
delete proxy.network;

View File

@@ -409,8 +409,10 @@ function URI_VLESS() {
proxy[`${params.security}-opts`] = opts;
}
}
proxy.network = params.type;
if (proxy.network === 'tcp' && params.headerType === 'http') {
proxy.network = 'http';
}
if (!proxy.network && isShadowrocket && params.obfs) {
proxy.network = params.obfs;
}
@@ -576,6 +578,10 @@ function Clash_All() {
}
}
if (proxy.fingerprint) {
proxy['tls-fingerprint'] = proxy.fingerprint;
}
if (proxy['benchmark-url']) {
proxy['test-url'] = proxy['benchmark-url'];
}
@@ -915,7 +921,8 @@ function Surge_External() {
line,
)?.[2];
}
// args = "-m", args = "rc4-md5"
// args = -m, args = rc4-md5
const argsRegex = /(,|^)\s*?args\s*?=\s*("(.*?)"|(.*?))(?=\s*?(,|$))/g;
let argsMatch;
const args = [];
@@ -926,6 +933,8 @@ function Surge_External() {
args.push(argsMatch[4]);
}
}
// addresses = "[ipv6]",,addresses = "ipv6", addresses = "ipv4"
// addresses = [ipv6], addresses = ipv6, addresses = ipv4
const addressesRegex =
/(,|^)\s*?addresses\s*?=\s*("(.*?)"|(.*?))(?=\s*?(,|$))/g;
let addressesMatch;

View File

@@ -46,8 +46,19 @@ function Clash() {
};
const parse = function (raw) {
// Clash YAML format
const proxies = safeLoad(raw).proxies;
return proxies.map((p) => JSON.stringify(p)).join('\n');
const {
proxies,
'global-client-fingerprint': globalClientFingerprint,
} = safeLoad(raw);
return proxies
.map((p) => {
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
if (globalClientFingerprint && !p['client-fingerprint']) {
p['client-fingerprint'] = globalClientFingerprint;
}
return JSON.stringify(p);
})
.join('\n');
};
return { name, test, parse };
}

View File

@@ -8,7 +8,7 @@ function HTML() {
function ClashProvider() {
const name = 'Clash Provider';
const test = (raw) => raw.indexOf('payload:') === 0;
const test = (raw) => /^payload:/gm.exec(raw).index >= 0;
const parse = (raw) => {
return raw.replace('payload:', '').replace(/^\s*-\s*/gm, '');
};

View File

@@ -40,7 +40,7 @@ async function doSync() {
platform: artifact.platform,
});
files[artifact.name] = {
files[encodeURIComponent(artifact.name)] = {
content: output,
};
}
@@ -54,10 +54,9 @@ async function doSync() {
if (artifact.sync) {
artifact.updated = new Date().getTime();
// extract real url from gist
artifact.url = body.files[artifact.name].raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
);
artifact.url = body.files[
encodeURIComponent(artifact.name)
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
}
}

View File

@@ -50,20 +50,32 @@ async function restoreArtifacts(_, res) {
throw new Error(`找不到 Sub-Store Gist 文件列表`);
}
const allArtifacts = $.read(ARTIFACTS_KEY);
const failed = [];
Object.keys(gist.files).map((key) => {
const filename = gist.files[key]?.filename;
if (filename) {
const artifact = findByName(allArtifacts, filename);
if (artifact) {
updateByName(allArtifacts, filename, {
...artifact,
url: gist.files[key]?.raw_url,
});
if (encodeURIComponent(filename) !== filename) {
$.error(`文件名 ${filename} 未编码 不保存`);
failed.push(filename);
} else {
allArtifacts.push({
name: `${filename}`,
url: gist.files[key]?.raw_url,
});
const artifact = findByName(allArtifacts, filename);
if (artifact) {
updateByName(allArtifacts, filename, {
...artifact,
url: gist.files[key]?.raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
),
});
} else {
allArtifacts.push({
name: `${filename}`,
url: gist.files[key]?.raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
),
});
}
}
}
});
@@ -193,9 +205,15 @@ async function deleteArtifact(req, res) {
if (artifact.updated) {
// delete gist
const files = {};
files[artifact.name] = {
files[encodeURIComponent(artifact.name)] = {
content: '',
};
if (encodeURIComponent(artifact.name) !== artifact.name) {
files[artifact.name] = {
content: '',
};
}
// 当别的Sub 删了同步订阅 或 gist里面删了 当前设备没有删除 时 无法删除的bug
try {
await syncToGist(files);

View File

@@ -16,6 +16,7 @@ import registerPreviewRoutes from './preview';
import registerSortingRoutes from './sort';
import registerMiscRoutes from './miscs';
import registerNodeInfoRoutes from './node-info';
import registerParserRoutes from './parser';
export default function serve() {
let port;
@@ -38,6 +39,7 @@ export default function serve() {
registerSyncRoutes($app);
registerNodeInfoRoutes($app);
registerMiscRoutes($app);
registerParserRoutes($app);
$app.start();

View File

@@ -0,0 +1,54 @@
import { success, failed } from '@/restful/response';
import { ProxyUtils } from '@/core/proxy-utils';
import { RuleUtils } from '@/core/rule-utils';
export default function register($app) {
$app.route('/api/proxy/parse').post(proxy_parser);
$app.route('/api/rule/parse').post(rule_parser);
}
/***
* 感谢 izhangxm 的 PR!
* 目前没有节点操作, 没有支持完整参数, 以后再完善一下
*/
/***
* 代理服务器协议转换接口。
* 请求方法为POST数据为json。需要提供data和client字段。
* data: string, 协议数据每行一个或者是clash
* client: string, 目标平台名称见backend/src/core/proxy-utils/producers/index.js
*
*/
function proxy_parser(req, res) {
const { data, client, content, platform } = req.body;
var result = {};
try {
var proxies = ProxyUtils.parse(data ?? content);
var par_res = ProxyUtils.produce(proxies, client ?? platform);
result['par_res'] = par_res;
} catch (err) {
failed(res, err);
return;
}
success(res, result);
}
/**
* 规则转换接口。
* 请求方法为POST数据为json。需要提供data和client字段。
* data: string, 多行规则字符串
* client: string, 目标平台名称具体见backend/src/core/rule-utils/producers.js
*/
function rule_parser(req, res) {
const { data, client, content, platform } = req.body;
var result = {};
try {
const rules = RuleUtils.parse(data ?? content);
var par_res = RuleUtils.produce(rules, client ?? platform);
result['par_res'] = par_res;
} catch (err) {
failed(res, err);
return;
}
success(res, result);
}

View File

@@ -447,23 +447,44 @@ async function syncArtifacts() {
const files = {};
try {
const invalid = [];
await Promise.all(
allArtifacts.map(async (artifact) => {
if (artifact.sync && artifact.source) {
$.info(`正在同步云配置:${artifact.name}...`);
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
});
try {
if (artifact.sync && artifact.source) {
$.info(`正在同步云配置:${artifact.name}...`);
const output = await produceArtifact({
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
produceOpts: {
'include-unsupported-proxy':
artifact.includeUnsupportedProxy,
},
});
files[artifact.name] = {
content: output,
};
// if (!output || output.length === 0)
// throw new Error('该配置的结果为空 不进行上传');
files[encodeURIComponent(artifact.name)] = {
content: output,
};
}
} catch (e) {
$.error(
`同步配置 ${artifact.name} 发生错误: ${e.message ?? e}`,
);
invalid.push(artifact.name);
}
}),
);
if (invalid.length > 0) {
throw new Error(
`同步配置 ${invalid.join(', ')} 发生错误 详情请查看日志`,
);
}
const resp = await syncToGist(files);
const body = JSON.parse(resp.body);
@@ -471,10 +492,9 @@ async function syncArtifacts() {
if (artifact.sync) {
artifact.updated = new Date().getTime();
// extract real url from gist
artifact.url = body.files[artifact.name].raw_url.replace(
/\/raw\/[^/]*\/(.*)/,
'/raw/$1',
);
artifact.url = body.files[
encodeURIComponent(artifact.name)
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
}
}
@@ -541,6 +561,9 @@ async function syncArtifact(req, res) {
type: artifact.type,
name: artifact.source,
platform: artifact.platform,
produceOpts: {
'include-unsupported-proxy': artifact.includeUnsupportedProxy,
},
});
$.info(
@@ -550,6 +573,8 @@ async function syncArtifact(req, res) {
2,
)}`,
);
// if (!output || output.length === 0)
// throw new Error('该配置的结果为空 不进行上传');
const resp = await syncToGist({
[encodeURIComponent(artifact.name)]: {
content: output,
@@ -559,11 +584,11 @@ async function syncArtifact(req, res) {
const body = JSON.parse(resp.body);
artifact.url = body.files[
encodeURIComponent(artifact.name)
].raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
]?.raw_url.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
$.write(allArtifacts, ARTIFACTS_KEY);
success(res, artifact);
} catch (err) {
$.error(`远程配置 ${artifact.name} 发生错误: ${err}`);
$.error(`远程配置 ${artifact.name} 发生错误: ${err.message ?? err}`);
failed(
res,
new InternalServerError(