mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2026-03-23 14:43:18 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72bc9b9456 | ||
|
|
3b4c14e7d0 | ||
|
|
59d93483bb |
@@ -31,7 +31,7 @@ Core functionalities:
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
- [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] Surge (SS, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, TUIC, Snell, Hysteria 2, SSH(Password authentication only), 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)
|
||||
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC)
|
||||
@@ -46,6 +46,7 @@ Core functionalities:
|
||||
- [x] Clash
|
||||
- [x] Surfboard
|
||||
- [x] Surge
|
||||
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
|
||||
- [x] Loon
|
||||
- [x] Shadowrocket
|
||||
- [x] QX
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.374",
|
||||
"version": "2.14.378",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -231,6 +231,7 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
||||
|
||||
// 处理 端口跳跃
|
||||
if (proxy.ports) {
|
||||
proxy.ports = String(proxy.ports);
|
||||
if (!['ClashMeta'].includes(targetPlatform)) {
|
||||
proxy.ports = proxy.ports.replace(/\//g, ',');
|
||||
}
|
||||
@@ -420,13 +421,13 @@ function lastParse(proxy) {
|
||||
proxy[`${proxy.network}-opts`].path = [transportPath];
|
||||
}
|
||||
}
|
||||
if (['hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||
if (proxy.ports) {
|
||||
proxy.ports = proxy.ports.replace(/\//g, ',');
|
||||
} else {
|
||||
delete proxy.ports;
|
||||
}
|
||||
// if (['hysteria', 'hysteria2', 'tuic'].includes(proxy.type)) {
|
||||
if (proxy.ports) {
|
||||
proxy.ports = String(proxy.ports).replace(/\//g, ',');
|
||||
} else {
|
||||
delete proxy.ports;
|
||||
}
|
||||
// }
|
||||
if (
|
||||
['hysteria2'].includes(proxy.type) &&
|
||||
proxy.obfs &&
|
||||
|
||||
@@ -15,6 +15,9 @@ const ipVersions = {
|
||||
export default function Surge_Producer() {
|
||||
const produce = (proxy, type, opts = {}) => {
|
||||
proxy.name = proxy.name.replace(/=|,/g, '');
|
||||
if (proxy.ports) {
|
||||
proxy.ports = String(proxy.ports);
|
||||
}
|
||||
switch (proxy.type) {
|
||||
case 'ss':
|
||||
return shadowsocks(proxy);
|
||||
@@ -676,7 +679,7 @@ function tuic(proxy) {
|
||||
);
|
||||
|
||||
if (isPresent(proxy, 'ports')) {
|
||||
result.append(`,port-hopping=${proxy.ports.replace(/,/g, ';')}`);
|
||||
result.append(`,port-hopping="${proxy.ports.replace(/,/g, ';')}"`);
|
||||
}
|
||||
|
||||
result.appendIfPresent(
|
||||
@@ -945,7 +948,7 @@ function hysteria2(proxy) {
|
||||
result.appendIfPresent(`,password=${proxy.password}`, 'password');
|
||||
|
||||
if (isPresent(proxy, 'ports')) {
|
||||
result.append(`,port-hopping=${proxy.ports.replace(/,/g, ';')}`);
|
||||
result.append(`,port-hopping="${proxy.ports.replace(/,/g, ';')}"`);
|
||||
}
|
||||
|
||||
result.appendIfPresent(
|
||||
|
||||
@@ -3,6 +3,7 @@ import $ from '@/core/app';
|
||||
import migrate from '@/utils/migration';
|
||||
import download from '@/utils/download';
|
||||
import { syncArtifacts } from '@/restful/sync';
|
||||
import { gistBackupAction } from '@/restful/miscs';
|
||||
|
||||
import registerSubscriptionRoutes from './subscriptions';
|
||||
import registerCollectionRoutes from './collections';
|
||||
@@ -44,20 +45,80 @@ export default function serve() {
|
||||
$app.start();
|
||||
|
||||
if ($.env.isNode) {
|
||||
const backend_cron = eval('process.env.SUB_STORE_BACKEND_CRON');
|
||||
if (backend_cron) {
|
||||
$.info(`[CRON] ${backend_cron} enabled`);
|
||||
const backend_sync_cron =
|
||||
eval('process.env.SUB_STORE_BACKEND_SYNC_CRON') ||
|
||||
eval('process.env.SUB_STORE_BACKEND_CRON');
|
||||
if (backend_sync_cron) {
|
||||
$.info(`[SYNC CRON] ${backend_sync_cron} enabled`);
|
||||
const { CronJob } = eval(`require("cron")`);
|
||||
new CronJob(
|
||||
backend_cron,
|
||||
backend_sync_cron,
|
||||
async function () {
|
||||
try {
|
||||
$.info(`[CRON] ${backend_cron} started`);
|
||||
$.info(`[SYNC CRON] ${backend_sync_cron} started`);
|
||||
await syncArtifacts();
|
||||
$.info(`[CRON] ${backend_cron} finished`);
|
||||
$.info(`[SYNC CRON] ${backend_sync_cron} finished`);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`[CRON] ${backend_cron} error: ${e.message ?? e}`,
|
||||
`[SYNC CRON] ${backend_sync_cron} error: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
// 'Asia/Shanghai' // timeZone
|
||||
);
|
||||
}
|
||||
const backend_download_cron = eval(
|
||||
'process.env.SUB_STORE_BACKEND_DOWNLOAD_CRON',
|
||||
);
|
||||
if (backend_download_cron) {
|
||||
$.info(`[DOWNLOAD CRON] ${backend_download_cron} enabled`);
|
||||
const { CronJob } = eval(`require("cron")`);
|
||||
new CronJob(
|
||||
backend_download_cron,
|
||||
async function () {
|
||||
try {
|
||||
$.info(
|
||||
`[DOWNLOAD CRON] ${backend_download_cron} started`,
|
||||
);
|
||||
await gistBackupAction('download');
|
||||
$.info(
|
||||
`[DOWNLOAD CRON] ${backend_download_cron} finished`,
|
||||
);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`[DOWNLOAD CRON] ${backend_download_cron} error: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
// 'Asia/Shanghai' // timeZone
|
||||
);
|
||||
}
|
||||
const backend_upload_cron = eval(
|
||||
'process.env.SUB_STORE_BACKEND_UPLOAD_CRON',
|
||||
);
|
||||
if (backend_upload_cron) {
|
||||
$.info(`[UPLOAD CRON] ${backend_upload_cron} enabled`);
|
||||
const { CronJob } = eval(`require("cron")`);
|
||||
new CronJob(
|
||||
backend_upload_cron,
|
||||
async function () {
|
||||
try {
|
||||
$.info(`[UPLOAD CRON] ${backend_upload_cron} started`);
|
||||
await gistBackupAction('upload');
|
||||
$.info(`[UPLOAD CRON] ${backend_upload_cron} finished`);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`[UPLOAD CRON] ${backend_upload_cron} error: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}, // onTick
|
||||
|
||||
@@ -80,10 +80,72 @@ async function refresh(_, res) {
|
||||
success(res);
|
||||
}
|
||||
|
||||
async function gistBackupAction(action) {
|
||||
// read token
|
||||
const { gistToken, syncPlatform } = $.read(SETTINGS_KEY);
|
||||
if (!gistToken) throw new Error('GitHub Token is required for backup!');
|
||||
|
||||
const gist = new Gist({
|
||||
token: gistToken,
|
||||
key: GIST_BACKUP_KEY,
|
||||
syncPlatform,
|
||||
});
|
||||
let content;
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
const updated = settings.syncTime;
|
||||
switch (action) {
|
||||
case 'upload':
|
||||
// update syncTime
|
||||
settings.syncTime = new Date().getTime();
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
content = $.read('#sub-store');
|
||||
if ($.env.isNode) content = JSON.stringify($.cache, null, ` `);
|
||||
$.info(`上传备份中...`);
|
||||
try {
|
||||
await gist.upload({
|
||||
[GIST_BACKUP_FILE_NAME]: { content },
|
||||
});
|
||||
$.info(`上传备份完成`);
|
||||
} catch (err) {
|
||||
// restore syncTime if upload failed
|
||||
settings.syncTime = updated;
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
$.info(`还原备份中...`);
|
||||
content = await gist.download(GIST_BACKUP_FILE_NAME);
|
||||
try {
|
||||
if (Object.keys(JSON.parse(content).settings).length === 0) {
|
||||
throw new Error('备份文件应该至少包含 settings 字段');
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||
}
|
||||
// restore settings
|
||||
$.write(content, '#sub-store');
|
||||
if ($.env.isNode) {
|
||||
content = JSON.parse(content);
|
||||
$.cache = content;
|
||||
$.persistCache();
|
||||
}
|
||||
$.info(`perform migration after restoring from gist...`);
|
||||
migrate();
|
||||
$.info(`migration completed`);
|
||||
$.info(`还原备份完成`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
async function gistBackup(req, res) {
|
||||
const { action } = req.query;
|
||||
// read token
|
||||
const { gistToken, syncPlatform } = $.read(SETTINGS_KEY);
|
||||
const { gistToken } = $.read(SETTINGS_KEY);
|
||||
if (!gistToken) {
|
||||
failed(
|
||||
res,
|
||||
@@ -93,68 +155,8 @@ async function gistBackup(req, res) {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const gist = new Gist({
|
||||
token: gistToken,
|
||||
key: GIST_BACKUP_KEY,
|
||||
syncPlatform,
|
||||
});
|
||||
try {
|
||||
let content;
|
||||
const settings = $.read(SETTINGS_KEY);
|
||||
const updated = settings.syncTime;
|
||||
switch (action) {
|
||||
case 'upload':
|
||||
// update syncTime
|
||||
settings.syncTime = new Date().getTime();
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
content = $.read('#sub-store');
|
||||
if ($.env.isNode)
|
||||
content = JSON.stringify($.cache, null, ` `);
|
||||
$.info(`上传备份中...`);
|
||||
try {
|
||||
await gist.upload({
|
||||
[GIST_BACKUP_FILE_NAME]: { content },
|
||||
});
|
||||
} catch (err) {
|
||||
// restore syncTime if upload failed
|
||||
settings.syncTime = updated;
|
||||
$.write(settings, SETTINGS_KEY);
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
$.info(`还原备份中...`);
|
||||
content = await gist.download(GIST_BACKUP_FILE_NAME);
|
||||
try {
|
||||
if (
|
||||
Object.keys(JSON.parse(content).settings).length ===
|
||||
0
|
||||
) {
|
||||
throw new Error(
|
||||
'备份文件应该至少包含 settings 字段',
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Gist 备份文件校验失败, 无法还原\nReason: ${
|
||||
err.message ?? err
|
||||
}`,
|
||||
);
|
||||
throw new Error('Gist 备份文件校验失败, 无法还原');
|
||||
}
|
||||
// restore settings
|
||||
$.write(content, '#sub-store');
|
||||
if ($.env.isNode) {
|
||||
content = JSON.parse(content);
|
||||
$.cache = content;
|
||||
$.persistCache();
|
||||
}
|
||||
$.info(`perform migration after restoring from gist...`);
|
||||
migrate();
|
||||
$.info(`migration completed`);
|
||||
$.info(`还原备份完成`);
|
||||
break;
|
||||
}
|
||||
await gistBackupAction(action);
|
||||
success(res);
|
||||
} catch (err) {
|
||||
$.error(
|
||||
@@ -171,3 +173,5 @@ async function gistBackup(req, res) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { gistBackupAction };
|
||||
|
||||
Reference in New Issue
Block a user