mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc58a5541e | ||
|
|
772f431887 | ||
|
|
2b60c515cd | ||
|
|
c8c22c3901 | ||
|
|
d8f9466b84 | ||
|
|
d12ccad382 | ||
|
|
b4358663cc | ||
|
|
aba6264988 | ||
|
|
2320ab3838 | ||
|
|
542957d34a | ||
|
|
07e50175f9 | ||
|
|
e09d66060d | ||
|
|
b048ecdfff | ||
|
|
aac72fb9a3 | ||
|
|
baec193e5c |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.402",
|
||||
"version": "2.14.415",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@@ -30,11 +30,10 @@
|
||||
"js-base64": "^3.7.2",
|
||||
"jsrsasign": "^11.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"ms": "^2.1.3",
|
||||
"nanoid": "^3.3.3",
|
||||
"request": "^2.88.2",
|
||||
"requests": "^0.3.0",
|
||||
"semver": "^7.3.7",
|
||||
"static-js-yaml": "^1.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
"static-js-yaml": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.0",
|
||||
|
||||
14661
backend/pnpm-lock.yaml
generated
14661
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ export const FILES_KEY = 'files';
|
||||
export const MODULES_KEY = 'modules';
|
||||
export const ARTIFACTS_KEY = 'artifacts';
|
||||
export const RULES_KEY = 'rules';
|
||||
export const TOKENS_KEY = 'tokens';
|
||||
export const GIST_BACKUP_KEY = 'Auto Generated Sub-Store Backup';
|
||||
export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
|
||||
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
|
||||
|
||||
@@ -327,6 +327,9 @@ function formatTransportPath(path) {
|
||||
}
|
||||
|
||||
function lastParse(proxy) {
|
||||
if (typeof proxy.cipher === 'string') {
|
||||
proxy.cipher = proxy.cipher.toLowerCase();
|
||||
}
|
||||
if (typeof proxy.password === 'number') {
|
||||
proxy.password = numberToString(proxy.password);
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ function URI_VMess() {
|
||||
);
|
||||
}
|
||||
// https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
|
||||
if (proxy.tls && proxy.sni) {
|
||||
if (proxy.tls && params.sni && params.sni !== '') {
|
||||
proxy.sni = params.sni;
|
||||
}
|
||||
let httpupgrade = false;
|
||||
@@ -531,6 +531,14 @@ function URI_VLESS() {
|
||||
if (Object.keys(opts).length > 0) {
|
||||
proxy[`${proxy.network}-opts`] = opts;
|
||||
}
|
||||
if (proxy.network === 'kcp') {
|
||||
// mKCP 种子。省略时不使用种子,但不可以为空字符串。建议 mKCP 用户使用 seed。
|
||||
if (params.seed) {
|
||||
proxy.seed = params.seed;
|
||||
}
|
||||
// mKCP 的伪装头部类型。当前可选值有 none / srtp / utp / wechat-video / dtls / wireguard。省略时默认值为 none,即不使用伪装头部,但不可以为空字符串。
|
||||
proxy.headerType = params.headerType || 'none';
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
|
||||
@@ -41,7 +41,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -219,7 +219,7 @@ obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||
uri = $[^,]+
|
||||
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||
@@ -240,6 +240,7 @@ idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"]
|
||||
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||
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(""); }
|
||||
|
||||
@@ -39,7 +39,7 @@ start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v
|
||||
return proxy;
|
||||
}
|
||||
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
|
||||
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
|
||||
proxy.type = "ss";
|
||||
// handle obfs
|
||||
if (obfs.type == "http" || obfs.type === "tls") {
|
||||
@@ -217,7 +217,7 @@ obfs_host = comma "obfs-host" equals host:domain { obfs.host = host; };
|
||||
obfs_uri = comma "obfs-uri" equals path:uri { obfs.path = path }
|
||||
uri = $[^,]+
|
||||
|
||||
udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; }
|
||||
udp_relay = comma "udp-relay" equals flag:bool { proxy.udp = flag; }
|
||||
fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; }
|
||||
reuse = comma "reuse" equals flag:bool { proxy.reuse = flag; }
|
||||
ecn = comma "ecn" equals flag:bool { proxy.ecn = flag; }
|
||||
@@ -238,6 +238,7 @@ idle_timeout = comma "idle-timeout" equals match:$[0-9]+ { proxy["idle-timeout"]
|
||||
private_key = comma "private-key" equals match:[^,]+ { proxy["keystore-private-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
server_fingerprint = comma "server-fingerprint" equals match:[^,]+ { proxy["server-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
|
||||
block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match.join(""); }
|
||||
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(""); }
|
||||
|
||||
@@ -125,6 +125,8 @@ function shadowsocks(proxy, includeUnsupportedProxy) {
|
||||
|
||||
// udp
|
||||
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');
|
||||
// udp-port
|
||||
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
|
||||
|
||||
// test-url
|
||||
result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url');
|
||||
|
||||
@@ -25,6 +25,10 @@ export default function SurgeMac_Producer() {
|
||||
`${proxy.name} is not supported on ${targetPlatform}, try to use Mihomo(SurgeMac - External Proxy Program) instead`,
|
||||
);
|
||||
return mihomo(proxy, type, opts);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Surge for macOS 可手动指定链接参数 target=SurgeMac 或在 同步配置 中指定 SurgeMac 来启用 mihomo 支援 Surge 本身不支持的协议`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,18 @@ export default function URI_Producer() {
|
||||
vlessTransportServiceName,
|
||||
)}`;
|
||||
}
|
||||
if (proxy.network === 'kcp') {
|
||||
if (proxy.seed) {
|
||||
vlessTransport += `&seed=${encodeURIComponent(
|
||||
proxy.seed,
|
||||
)}`;
|
||||
}
|
||||
if (proxy.headerType) {
|
||||
vlessTransport += `&headerType=${encodeURIComponent(
|
||||
proxy.headerType,
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
result = `vless://${proxy.uuid}@${proxy.server}:${
|
||||
proxy.port
|
||||
|
||||
@@ -21,6 +21,7 @@ import registerSettingRoutes from '@/restful/settings';
|
||||
import registerMiscRoutes from '@/restful/miscs';
|
||||
import registerSortRoutes from '@/restful/sort';
|
||||
import registerFileRoutes from '@/restful/file';
|
||||
import registerTokenRoutes from '@/restful/token';
|
||||
import registerModuleRoutes from '@/restful/module';
|
||||
|
||||
migrate();
|
||||
@@ -32,6 +33,7 @@ function serve() {
|
||||
// register routes
|
||||
registerCollectionRoutes($app);
|
||||
registerSubscriptionRoutes($app);
|
||||
registerTokenRoutes($app);
|
||||
registerFileRoutes($app);
|
||||
registerModuleRoutes($app);
|
||||
registerArtifactRoutes($app);
|
||||
|
||||
@@ -13,6 +13,9 @@ import { getISO } from '@/utils/geo';
|
||||
import env from '@/utils/env';
|
||||
|
||||
export default function register($app) {
|
||||
$app.get('/share/col/:name', downloadCollection);
|
||||
$app.get('/share/sub/:name', downloadSubscription);
|
||||
|
||||
$app.get('/download/collection/:name', downloadCollection);
|
||||
$app.get('/download/:name', downloadSubscription);
|
||||
$app.get(
|
||||
@@ -72,6 +75,7 @@ async function downloadSubscription(req, res) {
|
||||
includeUnsupportedProxy,
|
||||
resultFormat,
|
||||
proxy,
|
||||
noCache,
|
||||
} = req.query;
|
||||
let $options = {};
|
||||
if (req.query.$options) {
|
||||
@@ -128,6 +132,10 @@ async function downloadSubscription(req, res) {
|
||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||
}
|
||||
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
|
||||
const allSubs = $.read(SUBS_KEY);
|
||||
const sub = findByName(allSubs, name);
|
||||
if (sub) {
|
||||
@@ -148,6 +156,7 @@ async function downloadSubscription(req, res) {
|
||||
},
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -185,7 +194,7 @@ async function downloadSubscription(req, res) {
|
||||
if (!$arguments.noFlow) {
|
||||
// forward flow headers
|
||||
const flowInfo = await getFlowHeaders(
|
||||
url,
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
proxy || sub.proxy,
|
||||
@@ -280,6 +289,7 @@ async function downloadCollection(req, res) {
|
||||
includeUnsupportedProxy,
|
||||
resultFormat,
|
||||
proxy,
|
||||
noCache,
|
||||
} = req.query;
|
||||
|
||||
let $options = {};
|
||||
@@ -322,6 +332,9 @@ async function downloadCollection(req, res) {
|
||||
if (useMihomoExternal) {
|
||||
$.info(`手动指定了 target 为 SurgeMac, 将使用 Mihomo External`);
|
||||
}
|
||||
if (noCache) {
|
||||
$.info(`指定不使用缓存: ${noCache}`);
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
try {
|
||||
@@ -337,6 +350,7 @@ async function downloadCollection(req, res) {
|
||||
},
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
});
|
||||
|
||||
// forward flow header from the first subscription in this collection
|
||||
@@ -378,7 +392,7 @@ async function downloadCollection(req, res) {
|
||||
}
|
||||
if (!$arguments.noFlow) {
|
||||
const flowInfo = await getFlowHeaders(
|
||||
url,
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
proxy || sub.proxy || collection.proxy,
|
||||
|
||||
@@ -13,6 +13,8 @@ import { produceArtifact } from '@/restful/sync';
|
||||
export default function register($app) {
|
||||
if (!$.read(FILES_KEY)) $.write([], FILES_KEY);
|
||||
|
||||
$app.get('/share/file/:name', getFile);
|
||||
|
||||
$app.route('/api/file/:name')
|
||||
.get(getFile)
|
||||
.patch(updateFile)
|
||||
|
||||
@@ -4,11 +4,13 @@ import migrate from '@/utils/migration';
|
||||
import download from '@/utils/download';
|
||||
import { syncArtifacts } from '@/restful/sync';
|
||||
import { gistBackupAction } from '@/restful/miscs';
|
||||
import { TOKENS_KEY } from '@/constants';
|
||||
|
||||
import registerSubscriptionRoutes from './subscriptions';
|
||||
import registerCollectionRoutes from './collections';
|
||||
import registerArtifactRoutes from './artifacts';
|
||||
import registerFileRoutes from './file';
|
||||
import registerTokenRoutes from './token';
|
||||
import registerModuleRoutes from './module';
|
||||
import registerSyncRoutes from './sync';
|
||||
import registerDownloadRoutes from './download';
|
||||
@@ -36,6 +38,7 @@ export default function serve() {
|
||||
registerSettingRoutes($app);
|
||||
registerArtifactRoutes($app);
|
||||
registerFileRoutes($app);
|
||||
registerTokenRoutes($app);
|
||||
registerModuleRoutes($app);
|
||||
registerSyncRoutes($app);
|
||||
registerNodeInfoRoutes($app);
|
||||
@@ -143,7 +146,7 @@ export default function serve() {
|
||||
try {
|
||||
fs.accessSync(path.join(fe_abs_path, 'index.html'));
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
$.error(
|
||||
`[FRONTEND] index.html file not found in ${fe_abs_path}`,
|
||||
);
|
||||
}
|
||||
@@ -158,6 +161,7 @@ export default function serve() {
|
||||
|
||||
const staticFileMiddleware = express_.static(fe_path);
|
||||
|
||||
let be_share_rewrite = '/share/:type/:name';
|
||||
let be_api_rewrite = '';
|
||||
let be_download_rewrite = '';
|
||||
let be_api = '/api/';
|
||||
@@ -174,15 +178,39 @@ export default function serve() {
|
||||
be_download_rewrite = `${
|
||||
fe_be_path === '/' ? '' : fe_be_path
|
||||
}${be_download}`;
|
||||
|
||||
app.use(
|
||||
be_share_rewrite,
|
||||
createProxyMiddleware({
|
||||
target: `http://127.0.0.1:${port}`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: (path, req) => {
|
||||
if (req.method.toLowerCase() !== 'get')
|
||||
throw new Error('Method not allowed');
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
const token = tokens.find(
|
||||
(t) =>
|
||||
t.token === req.query.token &&
|
||||
t.type === req.params.type &&
|
||||
t.name === req.params.name &&
|
||||
(t.exp == null || t.exp > Date.now()),
|
||||
);
|
||||
if (!token) throw new Error('Forbbiden');
|
||||
return path;
|
||||
},
|
||||
}),
|
||||
);
|
||||
app.use(
|
||||
be_api_rewrite,
|
||||
createProxyMiddleware({
|
||||
target: `http://127.0.0.1:${port}`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: (path) => {
|
||||
return path.startsWith(be_api_rewrite)
|
||||
const newPath = path.startsWith(be_api_rewrite)
|
||||
? path.replace(be_api_rewrite, be_api)
|
||||
: path;
|
||||
return newPath.includes('?')
|
||||
? `${newPath}&share=true`
|
||||
: `${newPath}?share=true`;
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -220,6 +248,9 @@ export default function serve() {
|
||||
$.info(
|
||||
`[FRONTEND -> BACKEND] ${fe_address}:${fe_port}${be_download_rewrite} -> http://127.0.0.1:${port}${be_download}`,
|
||||
);
|
||||
$.info(
|
||||
`[SHARE BACKEND] ${fe_address}:${fe_port}${be_share_rewrite}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ export default function register($app) {
|
||||
}
|
||||
|
||||
function getEnv(req, res) {
|
||||
if (req.query.share) {
|
||||
env.feature.share = true;
|
||||
}
|
||||
success(res, env);
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ async function getFlowInfo(req, res) {
|
||||
}
|
||||
} else {
|
||||
const flowHeaders = await getFlowHeaders(
|
||||
url,
|
||||
$arguments?.insecure ? `${url}#insecure` : url,
|
||||
$arguments.flowUserAgent,
|
||||
undefined,
|
||||
sub.proxy,
|
||||
|
||||
@@ -39,6 +39,7 @@ async function produceArtifact({
|
||||
awaitCustomCache,
|
||||
$options,
|
||||
proxy,
|
||||
noCache,
|
||||
}) {
|
||||
platform = platform || 'JSON';
|
||||
|
||||
@@ -72,6 +73,7 @@ async function produceArtifact({
|
||||
proxy || sub.proxy,
|
||||
undefined,
|
||||
awaitCustomCache,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -119,6 +121,7 @@ async function produceArtifact({
|
||||
proxy || sub.proxy,
|
||||
undefined,
|
||||
awaitCustomCache,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -237,6 +240,9 @@ async function produceArtifact({
|
||||
proxy ||
|
||||
sub.proxy ||
|
||||
collection.proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -410,6 +416,9 @@ async function produceArtifact({
|
||||
ua || file.ua,
|
||||
undefined,
|
||||
file.proxy || proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
@@ -458,6 +467,9 @@ async function produceArtifact({
|
||||
ua || file.ua,
|
||||
undefined,
|
||||
file.proxy || proxy,
|
||||
undefined,
|
||||
undefined,
|
||||
noCache,
|
||||
);
|
||||
} catch (err) {
|
||||
errors[url] = err;
|
||||
|
||||
181
backend/src/restful/token.js
Normal file
181
backend/src/restful/token.js
Normal file
@@ -0,0 +1,181 @@
|
||||
import { deleteByName } from '@/utils/database';
|
||||
import { ENV } from '@/vendor/open-api';
|
||||
import { TOKENS_KEY, SUBS_KEY, FILES_KEY, COLLECTIONS_KEY } from '@/constants';
|
||||
import { failed, success } from '@/restful/response';
|
||||
import $ from '@/core/app';
|
||||
import { RequestInvalidError, InternalServerError } from '@/restful/errors';
|
||||
|
||||
export default function register($app) {
|
||||
if (!$.read(TOKENS_KEY)) $.write([], TOKENS_KEY);
|
||||
|
||||
$app.post('/api/token', signToken);
|
||||
|
||||
$app.route('/api/token/:token').delete(deleteToken);
|
||||
|
||||
$app.route('/api/tokens').get(getAllTokens);
|
||||
}
|
||||
|
||||
function deleteToken(req, res) {
|
||||
let { token } = req.params;
|
||||
token = decodeURIComponent(token);
|
||||
$.info(`正在删除:${token}`);
|
||||
let allTokens = $.read(TOKENS_KEY);
|
||||
deleteByName(allTokens, token, 'token');
|
||||
$.write(allTokens, TOKENS_KEY);
|
||||
success(res);
|
||||
}
|
||||
|
||||
function getAllTokens(req, res) {
|
||||
const { type, name } = req.query;
|
||||
const allTokens = $.read(TOKENS_KEY) || [];
|
||||
success(
|
||||
res,
|
||||
type || name
|
||||
? allTokens.filter(
|
||||
(item) =>
|
||||
(type ? item.type === type : true) &&
|
||||
(name ? item.name === name : true),
|
||||
)
|
||||
: allTokens,
|
||||
);
|
||||
}
|
||||
|
||||
async function signToken(req, res) {
|
||||
if (!ENV().isNode) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_ENV',
|
||||
`This endpoint is only available in Node.js environment`,
|
||||
),
|
||||
);
|
||||
}
|
||||
try {
|
||||
const { payload, options } = req.body;
|
||||
const ms = eval(`require("ms")`);
|
||||
let token = payload?.token;
|
||||
if (token != null) {
|
||||
if (typeof token !== 'string' || token.length < 1) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_CUSTOM_TOKEN',
|
||||
`Invalid custom token: ${token}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
if (tokens.find((t) => t.token === token)) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'DUPLICATE_TOKEN',
|
||||
`Token ${token} already exists`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
const type = payload?.type;
|
||||
const name = payload?.name;
|
||||
if (!type || !name)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_PAYLOAD',
|
||||
`payload type and name are required`,
|
||||
),
|
||||
);
|
||||
if (type === 'col') {
|
||||
const collections = $.read(COLLECTIONS_KEY) || [];
|
||||
const collection = collections.find((c) => c.name === name);
|
||||
if (!collection)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_COLLECTION',
|
||||
`collection ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else if (type === 'file') {
|
||||
const files = $.read(FILES_KEY) || [];
|
||||
const file = files.find((f) => f.name === name);
|
||||
if (!file)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_FILE',
|
||||
`file ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else if (type === 'sub') {
|
||||
const subs = $.read(SUBS_KEY) || [];
|
||||
const sub = subs.find((s) => s.name === name);
|
||||
if (!sub)
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_SUB',
|
||||
`sub ${name} not found`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_TYPE',
|
||||
`type ${name} not supported`,
|
||||
),
|
||||
);
|
||||
}
|
||||
let expiresIn = options?.expiresIn;
|
||||
if (options?.expiresIn != null) {
|
||||
expiresIn = ms(options.expiresIn);
|
||||
if (expiresIn == null || isNaN(expiresIn) || expiresIn <= 0) {
|
||||
return failed(
|
||||
res,
|
||||
new RequestInvalidError(
|
||||
'INVALID_EXPIRES_IN',
|
||||
`Invalid expiresIn option: ${options.expiresIn}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// const secret = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
|
||||
const nanoid = eval(`require("nanoid")`);
|
||||
const tokens = $.read(TOKENS_KEY) || [];
|
||||
// const now = Date.now();
|
||||
// for (const key in tokens) {
|
||||
// const token = tokens[key];
|
||||
// if (token.exp != null || token.exp < now) {
|
||||
// delete tokens[key];
|
||||
// }
|
||||
// }
|
||||
if (!token) {
|
||||
do {
|
||||
token = nanoid.customAlphabet(nanoid.urlAlphabet)();
|
||||
} while (tokens.find((t) => t.token === token));
|
||||
}
|
||||
tokens.push({
|
||||
...payload,
|
||||
token,
|
||||
createdAt: Date.now(),
|
||||
expiresIn: expiresIn > 0 ? options?.expiresIn : undefined,
|
||||
exp: expiresIn > 0 ? Date.now() + expiresIn : undefined,
|
||||
});
|
||||
|
||||
$.write(tokens, TOKENS_KEY);
|
||||
return success(res, {
|
||||
token,
|
||||
// secret,
|
||||
});
|
||||
} catch (e) {
|
||||
return failed(
|
||||
res,
|
||||
new InternalServerError(
|
||||
'TOKEN_SIGN_FAILED',
|
||||
`Failed to sign token`,
|
||||
`Reason: ${e.message ?? e}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
export function findByName(list, name) {
|
||||
return list.find((item) => item.name === name);
|
||||
export function findByName(list, name, field = 'name') {
|
||||
return list.find((item) => item[field] === name);
|
||||
}
|
||||
|
||||
export function findIndexByName(list, name) {
|
||||
return list.findIndex((item) => item.name === name);
|
||||
export function findIndexByName(list, name, field = 'name') {
|
||||
return list.findIndex((item) => item[field] === name);
|
||||
}
|
||||
|
||||
export function deleteByName(list, name) {
|
||||
const idx = findIndexByName(list, name);
|
||||
export function deleteByName(list, name, field = 'name') {
|
||||
const idx = findIndexByName(list, name, field);
|
||||
list.splice(idx, 1);
|
||||
}
|
||||
|
||||
export function updateByName(list, name, newItem) {
|
||||
const idx = findIndexByName(list, name);
|
||||
export function updateByName(list, name, newItem, field = 'name') {
|
||||
const idx = findIndexByName(list, name, field);
|
||||
list[idx] = newItem;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export default async function download(
|
||||
customProxy,
|
||||
skipCustomCache,
|
||||
awaitCustomCache,
|
||||
noCache,
|
||||
) {
|
||||
let $arguments = {};
|
||||
let url = rawUrl.replace(/#noFlow$/, '');
|
||||
@@ -65,7 +66,7 @@ export default async function download(
|
||||
if (customCacheKey && !skipCustomCache) {
|
||||
const customCached = $.read(customCacheKey);
|
||||
const cached = resourceCache.get(id);
|
||||
if (!$arguments?.noCache && cached) {
|
||||
if (!noCache && !$arguments?.noCache && cached) {
|
||||
$.info(
|
||||
`乐观缓存: URL ${url}\n存在有效的常规缓存\n使用常规缓存以避免重复请求`,
|
||||
);
|
||||
@@ -149,7 +150,7 @@ export default async function download(
|
||||
|
||||
// try to find in app cache
|
||||
const cached = resourceCache.get(id);
|
||||
if (!$arguments?.noCache && cached) {
|
||||
if (!noCache && !$arguments?.noCache && cached) {
|
||||
$.info(`使用缓存: ${url}`);
|
||||
result = cached;
|
||||
if (customCacheKey) {
|
||||
@@ -157,8 +158,13 @@ export default async function download(
|
||||
$.write(cached, customCacheKey);
|
||||
}
|
||||
} else {
|
||||
const insecure = $arguments?.insecure
|
||||
? isNode
|
||||
? { strictSSL: false }
|
||||
: { insecure: true }
|
||||
: undefined;
|
||||
$.info(
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nURL: ${url}`,
|
||||
`Downloading...\nUser-Agent: ${userAgent}\nTimeout: ${requestTimeout}\nProxy: ${proxy}\nInsecure: ${!!insecure}\nURL: ${url}`,
|
||||
);
|
||||
try {
|
||||
const { body, headers } = await http.get({
|
||||
@@ -167,6 +173,7 @@ export default async function download(
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
...(insecure ? insecure : {}),
|
||||
});
|
||||
|
||||
if (headers) {
|
||||
|
||||
@@ -24,6 +24,7 @@ if (isLanceX) backend = 'LanceX';
|
||||
if (isGUIforCores) backend = 'GUI.for.Cores';
|
||||
|
||||
let meta = {};
|
||||
let feature = {};
|
||||
|
||||
try {
|
||||
if (typeof $environment !== 'undefined') {
|
||||
@@ -63,5 +64,6 @@ try {
|
||||
export default {
|
||||
backend,
|
||||
version: substoreVersion,
|
||||
feature,
|
||||
meta,
|
||||
};
|
||||
|
||||
@@ -47,6 +47,11 @@ export async function getFlowHeaders(
|
||||
// $.info(`使用缓存的流量信息: ${url}`);
|
||||
flowInfo = cached;
|
||||
} else {
|
||||
const insecure = $arguments?.insecure
|
||||
? $.env.isNode
|
||||
? { strictSSL: false }
|
||||
: { insecure: true }
|
||||
: undefined;
|
||||
const { defaultProxy, defaultFlowUserAgent, defaultTimeout } =
|
||||
$.read(SETTINGS_KEY);
|
||||
let proxy = customProxy || defaultProxy;
|
||||
@@ -64,7 +69,7 @@ export async function getFlowHeaders(
|
||||
$.info(
|
||||
`使用 GET 方法从响应体获取流量信息: ${flowUrl}, User-Agent: ${
|
||||
userAgent || ''
|
||||
}`,
|
||||
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||
);
|
||||
const { body } = await http.get({
|
||||
url: flowUrl,
|
||||
@@ -72,6 +77,11 @@ export async function getFlowHeaders(
|
||||
'User-Agent': userAgent,
|
||||
},
|
||||
timeout: requestTimeout,
|
||||
...(proxy ? { proxy } : {}),
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
...(insecure ? insecure : {}),
|
||||
});
|
||||
flowInfo = body;
|
||||
} else {
|
||||
@@ -79,7 +89,7 @@ export async function getFlowHeaders(
|
||||
$.info(
|
||||
`使用 HEAD 方法从响应头获取流量信息: ${url}, User-Agent: ${
|
||||
userAgent || ''
|
||||
}, Proxy: ${proxy}`,
|
||||
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||
);
|
||||
const { headers } = await http.head({
|
||||
url: url
|
||||
@@ -103,20 +113,23 @@ export async function getFlowHeaders(
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
...(insecure ? insecure : {}),
|
||||
});
|
||||
flowInfo = getFlowField(headers);
|
||||
} catch (e) {
|
||||
$.error(
|
||||
`使用 HEAD 方法从响应头获取流量信息失败: ${url}, User-Agent: ${
|
||||
userAgent || ''
|
||||
}, Proxy: ${proxy}: ${e.message ?? e}`,
|
||||
}, Insecure: ${!!insecure}, Proxy: ${proxy}: ${
|
||||
e.message ?? e
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (!flowInfo) {
|
||||
$.info(
|
||||
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||
userAgent || ''
|
||||
}, Proxy: ${proxy}`,
|
||||
}, Insecure: ${!!insecure}, Proxy: ${proxy}`,
|
||||
);
|
||||
const { headers } = await http.get({
|
||||
url: url
|
||||
@@ -140,6 +153,7 @@ export async function getFlowHeaders(
|
||||
...(isLoon && proxy ? { node: proxy } : {}),
|
||||
...(isQX && proxy ? { opts: { policy: proxy } } : {}),
|
||||
...(proxy ? getPolicyDescriptor(proxy) : {}),
|
||||
...(insecure ? insecure : {}),
|
||||
});
|
||||
flowInfo = getFlowField(headers);
|
||||
}
|
||||
|
||||
@@ -474,4 +474,7 @@ export class MMDB {
|
||||
ipaso(ip) {
|
||||
return this.asnReader?.asn(ip)?.autonomousSystemOrganization;
|
||||
}
|
||||
ipasn(ip) {
|
||||
return this.asnReader?.asn(ip)?.autonomousSystemNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||
// Gist, // Gist 类
|
||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||
// }
|
||||
|
||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||
|
||||
Reference in New Issue
Block a user