mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2026-03-29 05:03:21 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfb0e60d34 | ||
|
|
2301ccbfb5 | ||
|
|
0b5761e5fc | ||
|
|
3ab21b0e26 | ||
|
|
89ab72e46c | ||
|
|
18bd6526d0 | ||
|
|
c7329c32eb | ||
|
|
4819ae95e4 | ||
|
|
370d228b04 | ||
|
|
d092916168 | ||
|
|
0c93de48ab |
@@ -26,11 +26,11 @@ Core functionalities:
|
||||
|
||||
### Supported Input Formats
|
||||
|
||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5)
|
||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] Clash Proxies YAML
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, WireGuard, VLESS, Hysteria 2)
|
||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), SSR(external, only for macOS), External Proxy Program(only for macOS), WireGuard(Surge to Surge))
|
||||
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard(Surfboard to Surfboard))
|
||||
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.285",
|
||||
"version": "2.14.296",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -11,7 +11,8 @@
|
||||
"dev:esbuild": "nodemon -w src -w package.json dev-esbuild.js",
|
||||
"dev:run": "nodemon -w sub-store.min.js sub-store.min.js",
|
||||
"build": "gulp",
|
||||
"bundle": "node bundle.js"
|
||||
"bundle": "node bundle.js",
|
||||
"changelog": "conventional-changelog -p cli -i CHANGELOG.md -s"
|
||||
},
|
||||
"author": "Peng-YM",
|
||||
"license": "GPL-3.0",
|
||||
|
||||
@@ -404,15 +404,19 @@ function lastParse(proxy) {
|
||||
}
|
||||
}
|
||||
if (typeof proxy.name !== 'string') {
|
||||
try {
|
||||
if (proxy.name?.data) {
|
||||
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
||||
} else {
|
||||
proxy.name = utf8ArrayToStr(proxy.name);
|
||||
if (/^\d+$/.test(proxy.name)) {
|
||||
proxy.name = `${proxy.name}`;
|
||||
} else {
|
||||
try {
|
||||
if (proxy.name?.data) {
|
||||
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
||||
} else {
|
||||
proxy.name = utf8ArrayToStr(proxy.name);
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(`proxy.name decode failed\nReason: ${e}`);
|
||||
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(`proxy.name decode failed\nReason: ${e}`);
|
||||
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||
}
|
||||
}
|
||||
if (['', 'off'].includes(proxy.sni)) {
|
||||
|
||||
@@ -996,6 +996,15 @@ function Loon_Http() {
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
function Loon_Socks5() {
|
||||
const name = 'Loon SOCKS5 Parser';
|
||||
const test = (line) => {
|
||||
return /^.*=\s*socks5/i.test(line.split(',')[0]);
|
||||
};
|
||||
|
||||
const parse = (line) => getLoonParser().parse(line);
|
||||
return { name, test, parse };
|
||||
}
|
||||
|
||||
function Loon_WireGuard() {
|
||||
const name = 'Loon WireGuard Parser';
|
||||
@@ -1302,6 +1311,7 @@ export default [
|
||||
Loon_Hysteria2(),
|
||||
Loon_Trojan(),
|
||||
Loon_Http(),
|
||||
Loon_Socks5(),
|
||||
Loon_WireGuard(),
|
||||
QX_SS(),
|
||||
QX_SSR(),
|
||||
|
||||
@@ -35,7 +35,7 @@ const grammars = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ https = tag equals "https"i address (username password)? (tls_host/tls_verificat
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
@@ -167,7 +170,7 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-name" 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; }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/hysteria2) {
|
||||
start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ https = tag equals "https"i address (username password)? (tls_host/tls_verificat
|
||||
http = tag equals "http"i address (username password)? (fast_open/udp_relay/others)* {
|
||||
proxy.type = "http";
|
||||
}
|
||||
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/fast_open/udp_relay/others)* {
|
||||
proxy.type = "socks5";
|
||||
}
|
||||
|
||||
address = comma server:server comma port:port {
|
||||
proxy.server = server;
|
||||
@@ -165,7 +168,7 @@ ssr_protocol_param = comma "protocol-param" equals param:$[^=,]+ { proxy["protoc
|
||||
vmess_alterId = comma "alterId" equals alterId:$[0-9]+ { proxy.alterId = parseInt(alterId); }
|
||||
|
||||
over_tls = comma "over-tls" equals flag:bool { proxy.tls = flag; }
|
||||
tls_host = comma "tls-name" 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; }
|
||||
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
|
||||
@@ -18,6 +18,8 @@ export default function Loon_Producer() {
|
||||
return vless(proxy);
|
||||
case 'http':
|
||||
return http(proxy);
|
||||
case 'socks5':
|
||||
return socks5(proxy);
|
||||
case 'wireguard':
|
||||
return wireguard(proxy);
|
||||
case 'hysteria2':
|
||||
@@ -316,6 +318,29 @@ function http(proxy) {
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
function socks5(proxy) {
|
||||
const result = new Result(proxy);
|
||||
result.append(`${proxy.name}=socks5,${proxy.server},${proxy.port}`);
|
||||
result.appendIfPresent(`,${proxy.username}`, 'username');
|
||||
result.appendIfPresent(`,"${proxy.password}"`, 'password');
|
||||
|
||||
// tls
|
||||
result.appendIfPresent(`,over-tls=${proxy.tls}`, 'tls');
|
||||
|
||||
// sni
|
||||
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
|
||||
|
||||
// tls verification
|
||||
result.appendIfPresent(
|
||||
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
|
||||
'skip-cert-verify',
|
||||
);
|
||||
|
||||
// tfo
|
||||
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
function wireguard(proxy) {
|
||||
if (Array.isArray(proxy.peers) && proxy.peers.length > 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isPresent, Result } from './utils';
|
||||
const targetPlatform = 'QX';
|
||||
|
||||
export default function QX_Producer() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const produce = (proxy, type, opts = {}) => {
|
||||
switch (proxy.type) {
|
||||
case 'ss':
|
||||
@@ -18,13 +19,7 @@ export default function QX_Producer() {
|
||||
case 'socks5':
|
||||
return socks5(proxy);
|
||||
case 'vless':
|
||||
if (opts['include-unsupported-proxy']) {
|
||||
return vless(proxy);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Platform ${targetPlatform}(App Store Release) does not support proxy type: ${proxy.type}`,
|
||||
);
|
||||
}
|
||||
return vless(proxy);
|
||||
}
|
||||
throw new Error(
|
||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ClashMeta_Producer from './clashmeta';
|
||||
import $ from '@/core/app';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
|
||||
const detourParser = (proxy, parsedProxy) => {
|
||||
if (proxy['dialer-proxy']) parsedProxy.detour = proxy['dialer-proxy'];
|
||||
@@ -620,8 +621,11 @@ const tuic5Parser = (proxy = {}) => {
|
||||
const wireguardParser = (proxy = {}) => {
|
||||
const local_address = ['ip', 'ipv6']
|
||||
.map((i) => proxy[i])
|
||||
.filter((i) => i)
|
||||
.map((i) => (/\\/.test(i) ? i : `${i}/32`));
|
||||
.map((i) => {
|
||||
if (isIPv4(i)) return `${i}/32`;
|
||||
if (isIPv6(i)) return `${i}/128`;
|
||||
})
|
||||
.filter((i) => i);
|
||||
const parsedProxy = {
|
||||
tag: proxy.name,
|
||||
type: 'wireguard',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
||||
import { ProxyUtils } from '@/core/proxy-utils';
|
||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||
import { findByName } from '@/utils/database';
|
||||
import { getFlowHeaders } from '@/utils/flow';
|
||||
@@ -29,11 +30,27 @@ export default function register($app) {
|
||||
req.query.resultFormat = 'nezha';
|
||||
await downloadSubscription(req, res);
|
||||
});
|
||||
$app.get(
|
||||
'/download/collection/:name/api/v1/monitor/:nezhaIndex',
|
||||
async (req, res) => {
|
||||
req.query.platform = 'JSON';
|
||||
req.query.produceType = 'internal';
|
||||
req.query.resultFormat = 'nezha-monitor';
|
||||
await downloadCollection(req, res);
|
||||
},
|
||||
);
|
||||
$app.get('/download/:name/api/v1/monitor/:nezhaIndex', async (req, res) => {
|
||||
req.query.platform = 'JSON';
|
||||
req.query.produceType = 'internal';
|
||||
req.query.resultFormat = 'nezha-monitor';
|
||||
await downloadSubscription(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadSubscription(req, res) {
|
||||
let { name } = req.params;
|
||||
let { name, nezhaIndex } = req.params;
|
||||
name = decodeURIComponent(name);
|
||||
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||
|
||||
const platform =
|
||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||
@@ -155,6 +172,15 @@ async function downloadSubscription(req, res) {
|
||||
if (platform === 'JSON') {
|
||||
if (resultFormat === 'nezha') {
|
||||
output = nezhaTransform(output);
|
||||
} else if (resultFormat === 'nezha-monitor') {
|
||||
nezhaIndex = /^\d+$/.test(nezhaIndex)
|
||||
? parseInt(nezhaIndex, 10)
|
||||
: output.findIndex((i) => i.name === nezhaIndex);
|
||||
output = await nezhaMonitor(
|
||||
output[nezhaIndex],
|
||||
nezhaIndex,
|
||||
req.query,
|
||||
);
|
||||
}
|
||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||
output,
|
||||
@@ -192,8 +218,9 @@ async function downloadSubscription(req, res) {
|
||||
}
|
||||
|
||||
async function downloadCollection(req, res) {
|
||||
let { name } = req.params;
|
||||
let { name, nezhaIndex } = req.params;
|
||||
name = decodeURIComponent(name);
|
||||
nezhaIndex = decodeURIComponent(nezhaIndex);
|
||||
|
||||
const platform =
|
||||
req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
|
||||
@@ -300,6 +327,15 @@ async function downloadCollection(req, res) {
|
||||
if (platform === 'JSON') {
|
||||
if (resultFormat === 'nezha') {
|
||||
output = nezhaTransform(output);
|
||||
} else if (resultFormat === 'nezha-monitor') {
|
||||
nezhaIndex = /^\d+$/.test(nezhaIndex)
|
||||
? parseInt(nezhaIndex, 10)
|
||||
: output.findIndex((i) => i.name === nezhaIndex);
|
||||
output = await nezhaMonitor(
|
||||
output[nezhaIndex],
|
||||
nezhaIndex,
|
||||
req.query,
|
||||
);
|
||||
}
|
||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||
output,
|
||||
@@ -338,6 +374,85 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function nezhaMonitor(proxy, index, query) {
|
||||
const result = {
|
||||
code: 0,
|
||||
message: 'success',
|
||||
result: [],
|
||||
};
|
||||
|
||||
try {
|
||||
const { isLoon, isSurge } = $.env;
|
||||
if (!isLoon && !isSurge)
|
||||
throw new Error('仅支持 Loon 和 Surge(ability=http-client-policy)');
|
||||
const node = ProxyUtils.produce([proxy], isLoon ? 'Loon' : 'Surge');
|
||||
if (!node) throw new Error('当前客户端不兼容此节点');
|
||||
const monitors = proxy._monitors || [
|
||||
{
|
||||
name: 'Cloudflare',
|
||||
url: 'http://cp.cloudflare.com/generate_204',
|
||||
method: 'HEAD',
|
||||
number: 3,
|
||||
timeout: 2000,
|
||||
},
|
||||
{
|
||||
name: 'Google',
|
||||
url: 'http://www.google.com/generate_204',
|
||||
method: 'HEAD',
|
||||
number: 3,
|
||||
timeout: 2000,
|
||||
},
|
||||
];
|
||||
const number =
|
||||
query.number || Math.max(...monitors.map((i) => i.number)) || 3;
|
||||
for (const monitor of monitors) {
|
||||
const interval = 10 * 60 * 1000;
|
||||
const data = {
|
||||
monitor_id: monitors.indexOf(monitor),
|
||||
server_id: index,
|
||||
monitor_name: monitor.name,
|
||||
server_name: proxy.name,
|
||||
created_at: [],
|
||||
avg_delay: [],
|
||||
};
|
||||
for (let index = 0; index < number; index++) {
|
||||
const startedAt = Date.now();
|
||||
try {
|
||||
await $.http[(monitor.method || 'HEAD').toLowerCase()]({
|
||||
timeout: monitor.timeout || 2000,
|
||||
url: monitor.url,
|
||||
'policy-descriptor': node,
|
||||
node,
|
||||
});
|
||||
const latency = Date.now() - startedAt;
|
||||
$.info(`${monitor.name} latency: ${latency}`);
|
||||
data.avg_delay.push(latency);
|
||||
} catch (e) {
|
||||
$.error(e);
|
||||
data.avg_delay.push(0);
|
||||
}
|
||||
|
||||
data.created_at.push(
|
||||
Date.now() - interval * (monitor.number - index - 1),
|
||||
);
|
||||
}
|
||||
|
||||
result.result.push(data);
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(e);
|
||||
result.result.push({
|
||||
monitor_id: 0,
|
||||
server_id: 0,
|
||||
monitor_name: `❌ ${e.message ?? e}`,
|
||||
server_name: proxy.name,
|
||||
created_at: [Date.now()],
|
||||
avg_delay: [0],
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(result, null, 2);
|
||||
}
|
||||
function nezhaTransform(output) {
|
||||
const result = {
|
||||
code: 0,
|
||||
@@ -376,7 +491,7 @@ function nezhaTransform(output) {
|
||||
Virtualization: '',
|
||||
BootTime: time,
|
||||
CountryCode, // 目前需要
|
||||
Version: '',
|
||||
Version: '0.0.1',
|
||||
},
|
||||
status: {
|
||||
CPU: 0,
|
||||
|
||||
@@ -40,7 +40,7 @@ const ISOFlags = {
|
||||
'🇭🇷': ['HR', 'HRV'],
|
||||
'🇭🇺': ['HU', 'HUN'],
|
||||
'🇯🇴': ['JO', 'JOR'],
|
||||
'🇯🇵': ['JP', 'JPN'],
|
||||
'🇯🇵': ['JP', 'JPN', 'TYO'],
|
||||
'🇰🇪': ['KE', 'KEN'],
|
||||
'🇰🇬': ['KG', 'KGZ'],
|
||||
'🇰🇭': ['KH', 'KGZ'],
|
||||
|
||||
Reference in New Issue
Block a user