Compare commits

...

14 Commits

Author SHA1 Message Date
xream
03e320cbd0 feat: 组合订阅中的单条订阅也支持透传 User-Agent
Some checks are pending
build / build (push) Waiting to run
2025-01-13 20:09:00 +08:00
xream
e325b9a39a feat: Loon 排除 XTLS; 切换使用 esbuild 打包 2025-01-13 16:03:52 +08:00
xream
87597f6fc2 ci: pnpm
Some checks are pending
build / build (push) Waiting to run
2025-01-13 14:44:34 +08:00
xream
3462d36c35 feat: Egern 和 Stash 可根据 User-Agent 自动包含官方/商店版/未续费订阅不支持的协议 2025-01-13 14:27:08 +08:00
xream
02946ec81c feat: Surge 默认开启 Shadowsocks 2022 2025-01-13 14:00:38 +08:00
xream
c963c872ff feat: Egern 使用 includeUnsupportedProxy 参数开启 Shadowsocks 2022
Some checks failed
build / build (push) Has been cancelled
2025-01-12 06:12:14 +08:00
xream
c4a1bb4ea1 feat: Loon 使用 includeUnsupportedProxy 参数开启 Shadowsocks/ShadowsocksR + Shadow TLS 2025-01-11 23:34:00 +08:00
xream
f96d9dea74 feat: 日志中增加上传配置的响应
Some checks failed
build / build (push) Has been cancelled
2025-01-09 18:56:36 +08:00
xream
01eb69d8ae ci: GitHub Action
Some checks are pending
build / build (push) Waiting to run
2025-01-09 09:35:07 +08:00
xream
797ba6f601 fix: 修复 Loon Shadow TLS 2025-01-09 09:30:16 +08:00
xream
128353a7f3 feat: gist 单页数量改为 100 2025-01-09 09:14:36 +08:00
xream
e6f6d51608 feat: Loon 使用 includeUnsupportedProxy 参数开启 Shadowsocks + Shadow TLS V3
Some checks are pending
build / build (push) Waiting to run
2025-01-08 22:52:00 +08:00
xream
589a6bfadb feat: Base64 Pre-processor 检测解码是否正常
Some checks are pending
build / build (push) Waiting to run
2025-01-08 20:13:46 +08:00
xream
75012503f8 fix: 修复 Clash Pre-processor 2025-01-08 19:49:34 +08:00
15 changed files with 6598 additions and 7256 deletions

View File

@@ -1,5 +1,6 @@
name: build name: build
on: on:
workflow_dispatch:
push: push:
branches: branches:
- master - master
@@ -26,18 +27,18 @@ jobs:
run: | run: |
npm install -g pnpm npm install -g pnpm
cd backend && pnpm i --no-frozen-lockfile cd backend && pnpm i --no-frozen-lockfile
- name: Test # - name: Test
run: | # run: |
cd backend # cd backend
pnpm test # pnpm test
- name: Build # - name: Build
run: | # run: |
cd backend # cd backend
pnpm run build # pnpm run build
- name: Bundle - name: Bundle
run: | run: |
cd backend cd backend
pnpm run bundle pnpm bundle:esbuild
- id: tag - id: tag
name: Generate release tag name: Generate release tag
run: | run: |

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.16.3", "version": "2.16.13",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
@@ -12,6 +12,7 @@
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js", "dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
"build": "gulp", "build": "gulp",
"bundle": "node bundle.js", "bundle": "node bundle.js",
"bundle:esbuild": "node bundle-esbuild.js",
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s" "changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
}, },
"author": "Peng-YM", "author": "Peng-YM",
@@ -33,6 +34,7 @@
"ms": "^2.1.3", "ms": "^2.1.3",
"nanoid": "^3.3.3", "nanoid": "^3.3.3",
"request": "^2.88.2", "request": "^2.88.2",
"semver": "^7.6.3",
"static-js-yaml": "^1.0.0" "static-js-yaml": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {

13502
backend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,12 +39,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy; return proxy;
} }
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{ shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)*{
proxy.type = "ssr"; proxy.type = "ssr";
// handle ssr obfs // handle ssr obfs
proxy.obfs = obfs.type; proxy.obfs = obfs.type;
} }
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* { shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "ss"; proxy.type = "ss";
// handle ss obfs // handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -169,6 +169,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); } vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; } tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; } tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }

View File

@@ -37,12 +37,12 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
return proxy; return proxy;
} }
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/others)*{ shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)*{
proxy.type = "ssr"; proxy.type = "ssr";
// handle ssr obfs // handle ssr obfs
proxy.obfs = obfs.type; proxy.obfs = obfs.type;
} }
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/others)* { shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/others)* {
proxy.type = "ss"; proxy.type = "ss";
// handle ss obfs // handle ss obfs
if (obfs.type == "http" || obfs.type === "tls") { if (obfs.type == "http" || obfs.type === "tls") {
@@ -167,6 +167,11 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); } vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
udp_port = comma "udp-port" equals match:$[0-9]+ { proxy["udp-port"] = parseInt(match.trim()); }
shadow_tls_version = comma "shadow-tls-version" equals match:$[0-9]+ { proxy["shadow-tls-version"] = parseInt(match.trim()); }
shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-sni"] = match.join(""); }
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join(""); }
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; } over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; } tls_host = comma sni:("tls-name"/"sni") equals host:domain { proxy.sni = host; }
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; } tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }

View File

@@ -1,5 +1,6 @@
import { safeLoad } from '@/utils/yaml'; import { safeLoad } from '@/utils/yaml';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import $ from '@/core/app';
function HTML() { function HTML() {
const name = 'HTML'; const name = 'HTML';
@@ -35,8 +36,15 @@ function Base64Encoded() {
); );
}; };
const parse = function (raw) { const parse = function (raw) {
raw = Base64.decode(raw); const decoded = Base64.decode(raw);
return raw; if (!/^\w+:\/\/\w+/m.test(decoded)) {
$.error(
`Base64 Pre-processor error: decoded line does not start with protocol`,
);
return raw;
}
return decoded;
}; };
return { name, test, parse }; return { name, test, parse };
} }
@@ -48,7 +56,7 @@ function Clash() {
const content = safeLoad(raw); const content = safeLoad(raw);
return content.proxies && Array.isArray(content.proxies); return content.proxies && Array.isArray(content.proxies);
}; };
const parse = function (raw) { const parse = function (raw, includeProxies) {
// Clash YAML format // Clash YAML format
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity // 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
@@ -56,35 +64,40 @@ function Clash() {
const afterReplace = raw.replace( const afterReplace = raw.replace(
/short-id:([ ]*[^,\n}]*)/g, /short-id:([ ]*[^,\n}]*)/g,
(matched, value) => { (matched, value) => {
const afterTrim = value.trim(); const afterTrim = value.trim();
// 为空 // 为空
if (!afterTrim || afterTrim === '') { if (!afterTrim || afterTrim === '') {
return 'short-id: ""' return 'short-id: ""';
} }
// 是否被引号包裹 // 是否被引号包裹
if (/^(['"]).*\1$/.test(afterTrim)) { if (/^(['"]).*\1$/.test(afterTrim)) {
return `short-id: ${afterTrim}`; return `short-id: ${afterTrim}`;
} else { } else {
return `short-id: "${afterTrim}"` return `short-id: "${afterTrim}"`;
} }
} },
); );
const { const {
proxies, proxies,
'global-client-fingerprint': globalClientFingerprint, 'global-client-fingerprint': globalClientFingerprint,
} = safeLoad(afterReplace); } = safeLoad(afterReplace);
return proxies return (
.map((p) => { (includeProxies ? 'proxies:\n' : '') +
// https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26 proxies
if (globalClientFingerprint && !p['client-fingerprint']) { .map((p) => {
p['client-fingerprint'] = globalClientFingerprint; // https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml#L73C1-L73C26
} if (globalClientFingerprint && !p['client-fingerprint']) {
return JSON.stringify(p); p['client-fingerprint'] = globalClientFingerprint;
}) }
.join('\n'); return `${includeProxies ? ' - ' : ''}${JSON.stringify(
p,
)}\n`;
})
.join('')
);
}; };
return { name, test, parse }; return { name, test, parse };
} }

View File

@@ -4,7 +4,7 @@ export default function Egern_Producer() {
// https://egernapp.com/zh-CN/docs/configuration/proxies // https://egernapp.com/zh-CN/docs/configuration/proxies
const list = proxies const list = proxies
.filter((proxy) => { .filter((proxy) => {
if (opts['include-unsupported-proxy']) return true; // if (opts['include-unsupported-proxy']) return true;
if ( if (
![ ![
'http', 'http',
@@ -47,6 +47,12 @@ export default function Egern_Producer() {
'salsa20', 'salsa20',
'chacha20', 'chacha20',
'chacha20-ietf', 'chacha20-ietf',
...(opts['include-unsupported-proxy']
? [
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
]
: []),
].includes(proxy.cipher))) || ].includes(proxy.cipher))) ||
(proxy.type === 'vmess' && (proxy.type === 'vmess' &&
(![ (![

View File

@@ -9,7 +9,7 @@ export default function Loon_Producer() {
case 'ss': case 'ss':
return shadowsocks(proxy, opts['include-unsupported-proxy']); return shadowsocks(proxy, opts['include-unsupported-proxy']);
case 'ssr': case 'ssr':
return shadowsocksr(proxy); return shadowsocksr(proxy, opts['include-unsupported-proxy']);
case 'trojan': case 'trojan':
return trojan(proxy); return trojan(proxy);
case 'vmess': case 'vmess':
@@ -32,7 +32,7 @@ export default function Loon_Producer() {
return { produce }; return { produce };
} }
function shadowsocks(proxy) { function shadowsocks(proxy, includeUnsupportedProxy) {
const result = new Result(proxy); const result = new Result(proxy);
if ( if (
![ ![
@@ -66,6 +66,8 @@ function shadowsocks(proxy) {
`${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`, `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
); );
let isShadowTLS;
// obfs // obfs
if (isPresent(proxy, 'plugin')) { if (isPresent(proxy, 'plugin')) {
if (proxy.plugin === 'obfs') { if (proxy.plugin === 'obfs') {
@@ -78,11 +80,52 @@ function shadowsocks(proxy) {
`,obfs-uri=${proxy['plugin-opts'].path}`, `,obfs-uri=${proxy['plugin-opts'].path}`,
'plugin-opts.path', 'plugin-opts.path',
); );
} else { } else if (!['shadow-tls'].includes(proxy.plugin)) {
throw new Error(`plugin ${proxy.plugin} is not supported`); throw new Error(`plugin ${proxy.plugin} is not supported`);
} }
} }
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host;
const version = proxy['plugin-opts'].version;
if (password) {
result.append(`,shadow-tls-password=${password}`);
if (host) {
result.append(`,shadow-tls-sni=${host}`);
}
if (version) {
if (version < 2) {
throw new Error(
`shadow-tls version ${version} is not supported`,
);
}
result.append(`,shadow-tls-version=${version}`);
}
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
isShadowTLS = true;
}
}
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -91,10 +134,16 @@ function shadowsocks(proxy) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
if (!includeUnsupportedProxy && isShadowTLS) {
throw new Error(
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
);
}
return result.toString(); return result.toString();
} }
function shadowsocksr(proxy) { function shadowsocksr(proxy, includeUnsupportedProxy) {
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(
`${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`, `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"`,
@@ -111,6 +160,49 @@ function shadowsocksr(proxy) {
result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs'); result.appendIfPresent(`,obfs=${proxy.obfs}`, 'obfs');
result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param'); result.appendIfPresent(`,obfs-param=${proxy['obfs-param']}`, 'obfs-param');
let isShadowTLS;
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
result.append(`,shadow-tls-password=${proxy['shadow-tls-password']}`);
result.appendIfPresent(
`,shadow-tls-version=${proxy['shadow-tls-version']}`,
'shadow-tls-version',
);
result.appendIfPresent(
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni',
);
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
isShadowTLS = true;
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host;
const version = proxy['plugin-opts'].version;
if (password) {
result.append(`,shadow-tls-password=${password}`);
if (host) {
result.append(`,shadow-tls-sni=${host}`);
}
if (version) {
if (version < 2) {
throw new Error(
`shadow-tls version ${version} is not supported`,
);
}
result.append(`,shadow-tls-version=${version}`);
}
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
isShadowTLS = true;
}
}
// tfo // tfo
result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo'); result.appendIfPresent(`,fast-open=${proxy.tfo}`, 'tfo');
@@ -119,6 +211,12 @@ function shadowsocksr(proxy) {
result.append(`,udp=true`); result.append(`,udp=true`);
} }
if (!includeUnsupportedProxy && isShadowTLS) {
throw new Error(
`shadow-tls is not supported(请使用 includeUnsupportedProxy 参数)`,
);
}
return result.toString(); return result.toString();
} }
@@ -252,8 +350,8 @@ function vmess(proxy) {
} }
function vless(proxy) { function vless(proxy) {
if (proxy['reality-opts']) { if (typeof proxy.flow !== 'undefined' || proxy['reality-opts']) {
throw new Error(`VLESS REALITY is unsupported`); throw new Error(`VLESS XTLS/REALITY is not supported`);
} }
const result = new Result(proxy); const result = new Result(proxy);
result.append( result.append(

View File

@@ -53,7 +53,7 @@ export default function Surge_Producer() {
return { produce }; return { produce };
} }
function shadowsocks(proxy, includeUnsupportedProxy) { function shadowsocks(proxy) {
const result = new Result(proxy); const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`); result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
if (!proxy.cipher) { if (!proxy.cipher) {
@@ -87,9 +87,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
'chacha20', 'chacha20',
'chacha20-ietf', 'chacha20-ietf',
'none', 'none',
...(includeUnsupportedProxy '2022-blake3-aes-128-gcm',
? ['2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm'] '2022-blake3-aes-256-gcm',
: []),
].includes(proxy.cipher) ].includes(proxy.cipher)
) { ) {
throw new Error(`cipher ${proxy.cipher} is not supported`); throw new Error(`cipher ${proxy.cipher} is not supported`);
@@ -127,8 +126,6 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
// udp // udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp'); result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
// test-url // test-url
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
@@ -160,6 +157,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
`,shadow-tls-sni=${proxy['shadow-tls-sni']}`, `,shadow-tls-sni=${proxy['shadow-tls-sni']}`,
'shadow-tls-sni', 'shadow-tls-sni',
); );
// udp-port
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
} else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) { } else if (['shadow-tls'].includes(proxy.plugin) && proxy['plugin-opts']) {
const password = proxy['plugin-opts'].password; const password = proxy['plugin-opts'].password;
const host = proxy['plugin-opts'].host; const host = proxy['plugin-opts'].host;
@@ -177,6 +176,11 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
} }
result.append(`,shadow-tls-version=${version}`); result.append(`,shadow-tls-version=${version}`);
} }
// udp-port
result.appendIfPresent(
`,udp-port=${proxy['udp-port']}`,
'udp-port',
);
} }
} }

View File

@@ -174,6 +174,14 @@ async function doSync() {
const resp = await syncToGist(files); const resp = await syncToGist(files);
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (artifact.sync) {

View File

@@ -1,4 +1,7 @@
import { getPlatformFromHeaders } from '@/utils/user-agent'; import {
getPlatformFromHeaders,
shouldIncludeUnsupportedProxy,
} from '@/utils/user-agent';
import { ProxyUtils } from '@/core/proxy-utils'; import { ProxyUtils } from '@/core/proxy-utils';
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants'; import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
import { findByName } from '@/utils/database'; import { findByName } from '@/utils/database';
@@ -161,7 +164,19 @@ async function downloadSubscription(req, res) {
} }
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy); includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`); $.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
}
if (
!includeUnsupportedProxy &&
shouldIncludeUnsupportedProxy(platform, reqUA)
) {
includeUnsupportedProxy = true;
$.info(
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
} }
if (useMihomoExternal) { if (useMihomoExternal) {
@@ -342,11 +357,9 @@ async function downloadCollection(req, res) {
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name); const collection = findByName(allCols, name);
const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info( $.info(
`正在下载组合订阅:${name}\n请求 User-Agent: ${ `正在下载组合订阅:${name}\n请求 User-Agent: ${reqUA}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
req.headers['user-agent'] || req.headers['User-Agent']
}\n请求 target: ${req.query.target}\n实际输出: ${platform}`,
); );
let { let {
@@ -393,7 +406,18 @@ async function downloadCollection(req, res) {
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy); includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info(`包含不支持的节点: ${includeUnsupportedProxy}`); $.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
}
if (
!includeUnsupportedProxy &&
shouldIncludeUnsupportedProxy(platform, reqUA)
) {
includeUnsupportedProxy = true;
$.info(
`当前客户端可包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
);
} }
if (useMihomoExternal) { if (useMihomoExternal) {
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`); $.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
@@ -417,6 +441,7 @@ async function downloadCollection(req, res) {
$options, $options,
proxy, proxy,
noCache, noCache,
ua: reqUA,
}); });
let subUserInfoOfSub; let subUserInfoOfSub;
// forward flow header from the first subscription in this collection // forward flow header from the first subscription in this collection

View File

@@ -216,6 +216,12 @@ async function produceArtifact({
await Promise.all( await Promise.all(
subnames.map(async (name) => { subnames.map(async (name) => {
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);
const passThroughUA = sub.passThroughUA;
if (passThroughUA) {
$.info(
`订阅开启了透传 User-Agent, 使用请求的 User-Agent: ${ua}`,
);
}
try { try {
$.info(`正在处理子订阅:${sub.name}...`); $.info(`正在处理子订阅:${sub.name}...`);
let raw; let raw;
@@ -237,7 +243,7 @@ async function produceArtifact({
try { try {
return await download( return await download(
url, url,
sub.ua, ua || sub.ua,
undefined, undefined,
proxy || proxy ||
sub.proxy || sub.proxy ||
@@ -625,6 +631,15 @@ async function syncArtifacts() {
const resp = await syncToGist(files); const resp = await syncToGist(files);
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
for (const artifact of allArtifacts) { for (const artifact of allArtifacts) {
if (artifact.sync) { if (artifact.sync) {
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
@@ -743,6 +758,16 @@ async function syncArtifact(req, res) {
}); });
artifact.updated = new Date().getTime(); artifact.updated = new Date().getTime();
const body = JSON.parse(resp.body); const body = JSON.parse(resp.body);
delete body.history;
delete body.forks;
delete body.owner;
Object.values(body.files).forEach((file) => {
delete file.content;
});
$.info('上传配置响应:');
$.info(JSON.stringify(body, null, 2));
let files = body.files; let files = body.files;
let isGitLab; let isGitLab;
if (Array.isArray(files)) { if (Array.isArray(files)) {

View File

@@ -207,7 +207,7 @@ export default async function download(
if (preprocess) { if (preprocess) {
try { try {
if (clashPreprocessor.test(body)) { if (clashPreprocessor.test(body)) {
body = clashPreprocessor.parse(body); body = clashPreprocessor.parse(body, true);
} }
} catch (e) { } catch (e) {
$.error(`Clash Pre-processor error: ${e}`); $.error(`Clash Pre-processor error: ${e}`);

View File

@@ -114,15 +114,17 @@ export default class Gist {
return; return;
}); });
} else { } else {
return this.http.get('/gists').then((response) => { return this.http
const gists = JSON.parse(response.body); .get('/gists?per_page=100&page=1')
for (let g of gists) { .then((response) => {
if (g.description === this.key) { const gists = JSON.parse(response.body);
return g; for (let g of gists) {
if (g.description === this.key) {
return g;
}
} }
} return;
return; });
});
} }
} }

View File

@@ -1,3 +1,7 @@
import gte from 'semver/functions/gte';
import coerce from 'semver/functions/coerce';
import $ from '@/core/app';
export function getUserAgentFromHeaders(headers) { export function getUserAgentFromHeaders(headers) {
const keys = Object.keys(headers); const keys = Object.keys(headers);
let UA = ''; let UA = '';
@@ -56,3 +60,17 @@ export function getPlatformFromHeaders(headers) {
const { UA, ua, accept } = getUserAgentFromHeaders(headers); const { UA, ua, accept } = getUserAgentFromHeaders(headers);
return getPlatformFromUserAgent({ ua, UA, accept }); return getPlatformFromUserAgent({ ua, UA, accept });
} }
export function shouldIncludeUnsupportedProxy(platform, ua) {
try {
const version = coerce(ua).version;
if (platform === 'Stash' && gte(version, '2.8.0')) {
return true;
}
if (platform === 'Egern' && gte(version, '1.29.0')) {
return true;
}
} catch (e) {
$.error(`获取版本号失败: ${e}`);
}
return false;
}