Compare commits

..

15 Commits

Author SHA1 Message Date
xream
feb5bc5765 feat: Egern 模块 2025-08-10 00:30:43 +08:00
xream
0f0812ee6c doc: README
Some checks failed
build / build (push) Has been cancelled
2025-08-08 14:30:52 +08:00
xream
6be158f17a feat: 修改 releases 链接 2025-08-08 12:46:27 +08:00
xream
c7c9b21f79 feat: 本地订阅支持混写单行 YAML
Some checks failed
build / build (push) Has been cancelled
2025-08-04 10:48:52 +08:00
xream
d84f761b5d feat: Loon tls-name 和 sni 若同时存在, 以后面的为准 2025-08-03 15:36:09 +08:00
xream
b7d7346ef7 feat: geo 增加多哥和伊拉克 2025-08-03 10:27:46 +08:00
xream
cbfe528c5e feat: 订阅流量信息中的 expire <=0 时, 视为空 2025-08-01 13:09:11 +08:00
xream
59ea2bd174 feat: 获取订阅时, 总是检查是否包含有效节点 2025-07-29 11:59:55 +08:00
xream
1c9ae2a079 feat: 支持 GitHub 加速代理(前端需 >= 2.15.58) 2025-07-28 14:31:39 +08:00
xream
22e39dc18f feat: 单条订阅确保没有 subscriptions 字段 2025-07-28 11:26:18 +08:00
xream
4276869033 refactor: 非 Node 环境的 req query params decode 2025-07-27 22:47:43 +08:00
xream
c902ad8c87 fix: 修复 sing-box vless 传输层
Some checks failed
build / build (push) Has been cancelled
2025-07-27 00:36:37 +08:00
xream
10fe493162 feat: Egern shadowsocks+shadow-tls 支持 udp port
Some checks failed
build / build (push) Has been cancelled
2025-07-26 15:57:47 +08:00
xream
3f9867512b feat: 流量四舍五入优化
Some checks failed
build / build (push) Has been cancelled
2025-07-22 17:40:50 +08:00
xream
8ab694efcc doc: README 2025-07-21 09:33:31 +08:00
31 changed files with 151 additions and 165 deletions

View File

@@ -99,22 +99,16 @@ Go to `backend` directories, install node dependencies:
pnpm i pnpm i
``` ```
1. In `backend`, run the backend server on http://localhost:3000
babel(old school)
```
pnpm start
```
or
esbuild(experimental)
``` ```
SUB_STORE_BACKEND_API_PORT=3000 pnpm run --parallel "/^dev:.*/" SUB_STORE_BACKEND_API_PORT=3000 pnpm run --parallel "/^dev:.*/"
``` ```
### Build
```
pnpm bundle:esbuild
```
## LICENSE ## LICENSE
This project is under the GPL V3 LICENSE. This project is under the GPL V3 LICENSE.

View File

@@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.19.91", "version": "2.20.2",
"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": {

View File

@@ -13,6 +13,7 @@ import getQXParser from './peggy/qx';
import getTrojanURIParser from './peggy/trojan-uri'; import getTrojanURIParser from './peggy/trojan-uri';
import $ from '@/core/app'; import $ from '@/core/app';
import JSON5 from 'json5'; import JSON5 from 'json5';
import YAML from '@/utils/yaml';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
@@ -1130,15 +1131,21 @@ function URI_Trojan() {
function Clash_All() { function Clash_All() {
const name = 'Clash Parser'; const name = 'Clash Parser';
const test = (line) => { const test = (line) => {
let proxy;
try { try {
JSON5.parse(line); proxy = JSON5.parse(line);
} catch (e) { } catch (e) {
return false; proxy = YAML.parse(line);
} }
return true; return !!proxy?.type;
}; };
const parse = (line) => { const parse = (line) => {
const proxy = JSON5.parse(line); let proxy;
try {
proxy = JSON5.parse(line);
} catch (e) {
proxy = YAML.parse(line);
}
if ( if (
![ ![
'anytls', 'anytls',

View File

@@ -54,31 +54,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path); $set(proxy, "plugin-opts.path", obfs.path);
} }
} }
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* { vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0; proxy.alterId = proxy.alterId || 0;
handleTransport(); handleTransport();
} }
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* { vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
proxy.type = "vless"; proxy.type = "vless";
handleTransport(); handleTransport();
} }
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "trojan"; proxy.type = "trojan";
handleTransport(); handleTransport();
} }
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* { hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
proxy.type = "hysteria2"; proxy.type = "hysteria2";
} }
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
proxy.tls = true; proxy.tls = true;
} }
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* { http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
} }
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
} }
@@ -175,7 +175,8 @@ shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-s
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = 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_name = comma sni:("tls-name") equals host:domain { proxy.sni = host; }
sni = comma sni:("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; }
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); } tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); } tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }

View File

@@ -52,31 +52,31 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
$set(proxy, "plugin-opts.path", obfs.path); $set(proxy, "plugin-opts.path", obfs.path);
} }
} }
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* { vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
proxy.type = "vmess"; proxy.type = "vmess";
proxy.cipher = proxy.cipher || "none"; proxy.cipher = proxy.cipher || "none";
proxy.alterId = proxy.alterId || 0; proxy.alterId = proxy.alterId || 0;
handleTransport(); handleTransport();
} }
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* { vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
proxy.type = "vless"; proxy.type = "vless";
handleTransport(); handleTransport();
} }
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "trojan"; proxy.type = "trojan";
handleTransport(); handleTransport();
} }
hysteria2 = tag equals "hysteria2"i address password (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* { hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/salamander_password/ecn/ip_mode/block_quic/others)* {
proxy.type = "hysteria2"; proxy.type = "hysteria2";
} }
https = tag equals "https"i address (username password)? (tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
proxy.tls = true; proxy.tls = true;
} }
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* { http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "http"; proxy.type = "http";
} }
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_host/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* { socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
proxy.type = "socks5"; proxy.type = "socks5";
} }
@@ -173,7 +173,8 @@ shadow_tls_sni = comma "shadow-tls-sni" equals match:[^,]+ { proxy["shadow-tls-s
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = 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_name = comma sni:("tls-name") equals host:domain { proxy.sni = host; }
sni = comma sni:("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; }
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); } tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); } tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }

View File

@@ -371,6 +371,14 @@ export default function Egern_Producer() {
}; };
} }
} }
if (
['ss'].includes(original.type) &&
proxy.shadow_tls &&
original['udp-port'] > 0 &&
original['udp-port'] <= 65535
) {
proxy['udp_port'] = original['udp-port'];
}
delete proxy.subName; delete proxy.subName;
delete proxy.collectionName; delete proxy.collectionName;

View File

@@ -181,7 +181,7 @@ const h1Parser = (proxy, parsedProxy) => {
host = `${host}`.split(',').map((i) => i.trim()); host = `${host}`.split(',').map((i) => i.trim());
if (host.length > 0) transport.host = host; if (host.length > 0) transport.host = host;
} }
if (!transport.host) return; // if (!transport.host) return;
if (proxy['http-path'] && proxy['http-path'] !== '') { if (proxy['http-path'] && proxy['http-path'] !== '') {
const path = proxy['http-path']; const path = proxy['http-path'];
if (Array.isArray(path)) { if (Array.isArray(path)) {
@@ -190,7 +190,7 @@ const h1Parser = (proxy, parsedProxy) => {
} }
if (parsedProxy.tls.insecure) if (parsedProxy.tls.insecure)
parsedProxy.tls.server_name = transport.host[0]; parsedProxy.tls.server_name = transport.host[0];
if (transport.host.length === 1) transport.host = transport.host[0]; if (transport.host?.length === 1) transport.host = transport.host[0];
for (const key of Object.keys(transport.headers)) { for (const key of Object.keys(transport.headers)) {
const value = transport.headers[key]; const value = transport.headers[key];
if (value.length === 1) transport.headers[key] = value[0]; if (value.length === 1) transport.headers[key] = value[0];
@@ -569,6 +569,8 @@ const vlessParser = (proxy = {}) => {
// if (['xtls-rprx-vision', ''].includes(proxy.flow)) parsedProxy.flow = proxy.flow; // if (['xtls-rprx-vision', ''].includes(proxy.flow)) parsedProxy.flow = proxy.flow;
if (proxy.flow != null) parsedProxy.flow = proxy.flow; if (proxy.flow != null) parsedProxy.flow = proxy.flow;
if (proxy.network === 'ws') wsParser(proxy, parsedProxy); if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
if (proxy.network === 'http') h1Parser(proxy, parsedProxy);
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy); if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
networkParser(proxy, parsedProxy); networkParser(proxy, parsedProxy);
tfoParser(proxy, parsedProxy); tfoParser(proxy, parsedProxy);

View File

@@ -112,7 +112,6 @@ function replaceArtifact(req, res) {
async function getArtifact(req, res) { async function getArtifact(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
const allArtifacts = $.read(ARTIFACTS_KEY); const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name); const artifact = findByName(allArtifacts, name);
@@ -163,7 +162,6 @@ function createArtifact(req, res) {
function updateArtifact(req, res) { function updateArtifact(req, res) {
const allArtifacts = $.read(ARTIFACTS_KEY); const allArtifacts = $.read(ARTIFACTS_KEY);
let oldName = req.params.name; let oldName = req.params.name;
oldName = decodeURIComponent(oldName);
const artifact = findByName(allArtifacts, oldName); const artifact = findByName(allArtifacts, oldName);
if (artifact) { if (artifact) {
$.info(`正在更新远程配置:${artifact.name}`); $.info(`正在更新远程配置:${artifact.name}`);
@@ -197,7 +195,6 @@ function updateArtifact(req, res) {
async function deleteArtifact(req, res) { async function deleteArtifact(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除远程配置:${name}`); $.info(`正在删除远程配置:${name}`);
const allArtifacts = $.read(ARTIFACTS_KEY); const allArtifacts = $.read(ARTIFACTS_KEY);
try { try {

View File

@@ -52,7 +52,6 @@ function createCollection(req, res) {
function getCollection(req, res) { function getCollection(req, res) {
let { name } = req.params; let { name } = req.params;
let { raw } = req.query; let { raw } = req.query;
name = decodeURIComponent(name);
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name); const collection = findByName(allCols, name);
if (collection) { if (collection) {
@@ -84,7 +83,6 @@ function getCollection(req, res) {
function updateCollection(req, res) { function updateCollection(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
let collection = req.body; let collection = req.body;
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const oldCol = findByName(allCols, name); const oldCol = findByName(allCols, name);
@@ -137,7 +135,6 @@ function updateCollection(req, res) {
function deleteCollection(req, res) { function deleteCollection(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除组合订阅:${name}`); $.info(`正在删除组合订阅:${name}`);
let allCols = $.read(COLLECTIONS_KEY); let allCols = $.read(COLLECTIONS_KEY);
deleteByName(allCols, name); deleteByName(allCols, name);

View File

@@ -88,8 +88,6 @@ export default function register($app) {
async function downloadSubscription(req, res) { async function downloadSubscription(req, res) {
let { name, nezhaIndex } = req.params; let { name, nezhaIndex } = req.params;
name = decodeURIComponent(name);
nezhaIndex = decodeURIComponent(nezhaIndex);
const useMihomoExternal = req.query.target === 'SurgeMac'; const useMihomoExternal = req.query.target === 'SurgeMac';
@@ -140,7 +138,6 @@ async function downloadSubscription(req, res) {
$.info(`传入 $options: ${JSON.stringify($options)}`); $.info(`传入 $options: ${JSON.stringify($options)}`);
} }
if (url) { if (url) {
url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`); $.info(`指定远程订阅 URL: ${url}`);
if (!/^https?:\/\//.test(url)) { if (!/^https?:\/\//.test(url)) {
content = url; content = url;
@@ -148,32 +145,25 @@ async function downloadSubscription(req, res) {
} }
} }
if (content) { if (content) {
content = decodeURIComponent(content);
$.info(`指定本地订阅: ${content}`); $.info(`指定本地订阅: ${content}`);
} }
if (proxy) { if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`); $.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
} }
if (ua) { if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程订阅 User-Agent: ${ua}`); $.info(`指定远程订阅 User-Agent: ${ua}`);
} }
if (mergeSources) { if (mergeSources) {
mergeSources = decodeURIComponent(mergeSources);
$.info(`指定合并来源: ${mergeSources}`); $.info(`指定合并来源: ${mergeSources}`);
} }
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`); $.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
} }
if (produceType) { if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`); $.info(`指定生产类型: ${produceType}`);
} }
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info( $.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`, `包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
); );
@@ -362,8 +352,6 @@ async function downloadSubscription(req, res) {
async function downloadCollection(req, res) { async function downloadCollection(req, res) {
let { name, nezhaIndex } = req.params; let { name, nezhaIndex } = req.params;
name = decodeURIComponent(name);
nezhaIndex = decodeURIComponent(nezhaIndex);
const useMihomoExternal = req.query.target === 'SurgeMac'; const useMihomoExternal = req.query.target === 'SurgeMac';
@@ -416,21 +404,17 @@ async function downloadCollection(req, res) {
} }
if (proxy) { if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`); $.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
} }
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
$.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`); $.info(`指定忽略失败的远程订阅: ${ignoreFailedRemoteSub}`);
} }
if (produceType) { if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`); $.info(`指定生产类型: ${produceType}`);
} }
if (includeUnsupportedProxy) { if (includeUnsupportedProxy) {
includeUnsupportedProxy = decodeURIComponent(includeUnsupportedProxy);
$.info( $.info(
`包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`, `包含官方/商店版/未续费订阅不支持的协议: ${includeUnsupportedProxy}`,
); );

View File

@@ -51,7 +51,6 @@ function createFile(req, res) {
async function getFile(req, res) { async function getFile(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
const reqUA = req.headers['user-agent'] || req.headers['User-Agent']; const reqUA = req.headers['user-agent'] || req.headers['User-Agent'];
$.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`); $.info(`正在下载文件:${name}\n请求 User-Agent: ${reqUA}`);
let { let {
@@ -95,42 +94,33 @@ async function getFile(req, res) {
$.info(`传入 $options: ${JSON.stringify($options)}`); $.info(`传入 $options: ${JSON.stringify($options)}`);
} }
if (url) { if (url) {
url = decodeURIComponent(url);
$.info(`指定远程文件 URL: ${url}`); $.info(`指定远程文件 URL: ${url}`);
} }
if (proxy) { if (proxy) {
proxy = decodeURIComponent(proxy);
$.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`); $.info(`指定远程订阅使用代理/策略 proxy: ${proxy}`);
} }
if (ua) { if (ua) {
ua = decodeURIComponent(ua);
$.info(`指定远程文件 User-Agent: ${ua}`); $.info(`指定远程文件 User-Agent: ${ua}`);
} }
if (subInfoUrl) { if (subInfoUrl) {
subInfoUrl = decodeURIComponent(subInfoUrl);
$.info(`指定获取流量的 subInfoUrl: ${subInfoUrl}`); $.info(`指定获取流量的 subInfoUrl: ${subInfoUrl}`);
} }
if (subInfoUserAgent) { if (subInfoUserAgent) {
subInfoUserAgent = decodeURIComponent(subInfoUserAgent);
$.info(`指定获取流量的 subInfoUserAgent: ${subInfoUserAgent}`); $.info(`指定获取流量的 subInfoUserAgent: ${subInfoUserAgent}`);
} }
if (content) { if (content) {
content = decodeURIComponent(content);
$.info(`指定本地文件: ${content}`); $.info(`指定本地文件: ${content}`);
} }
if (mergeSources) { if (mergeSources) {
mergeSources = decodeURIComponent(mergeSources);
$.info(`指定合并来源: ${mergeSources}`); $.info(`指定合并来源: ${mergeSources}`);
} }
if (ignoreFailedRemoteFile != null && ignoreFailedRemoteFile !== '') { if (ignoreFailedRemoteFile != null && ignoreFailedRemoteFile !== '') {
ignoreFailedRemoteFile = decodeURIComponent(ignoreFailedRemoteFile);
$.info(`指定忽略失败的远程文件: ${ignoreFailedRemoteFile}`); $.info(`指定忽略失败的远程文件: ${ignoreFailedRemoteFile}`);
} }
if (noCache) { if (noCache) {
$.info(`指定不使用缓存: ${noCache}`); $.info(`指定不使用缓存: ${noCache}`);
} }
if (produceType) { if (produceType) {
produceType = decodeURIComponent(produceType);
$.info(`指定生产类型: ${produceType}`); $.info(`指定生产类型: ${produceType}`);
} }
@@ -225,7 +215,6 @@ async function getFile(req, res) {
function getWholeFile(req, res) { function getWholeFile(req, res) {
let { name } = req.params; let { name } = req.params;
let { raw } = req.query; let { raw } = req.query;
name = decodeURIComponent(name);
const allFiles = $.read(FILES_KEY); const allFiles = $.read(FILES_KEY);
const file = findByName(allFiles, name); const file = findByName(allFiles, name);
if (file) { if (file) {
@@ -257,7 +246,6 @@ function getWholeFile(req, res) {
function updateFile(req, res) { function updateFile(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
let file = req.body; let file = req.body;
const allFiles = $.read(FILES_KEY); const allFiles = $.read(FILES_KEY);
const oldFile = findByName(allFiles, name); const oldFile = findByName(allFiles, name);
@@ -299,7 +287,6 @@ function updateFile(req, res) {
function deleteFile(req, res) { function deleteFile(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除文件:${name}`); $.info(`正在删除文件:${name}`);
let allFiles = $.read(FILES_KEY); let allFiles = $.read(FILES_KEY);
deleteByName(allFiles, name); deleteByName(allFiles, name);

View File

@@ -43,7 +43,6 @@ function createModule(req, res) {
function getModule(req, res) { function getModule(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
const allModules = $.read(MODULES_KEY); const allModules = $.read(MODULES_KEY);
const module = findByName(allModules, name); const module = findByName(allModules, name);
if (module) { if (module) {
@@ -64,7 +63,6 @@ function getModule(req, res) {
function updateModule(req, res) { function updateModule(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
let module = req.body; let module = req.body;
const allModules = $.read(MODULES_KEY); const allModules = $.read(MODULES_KEY);
const oldModule = findByName(allModules, name); const oldModule = findByName(allModules, name);
@@ -92,7 +90,6 @@ function updateModule(req, res) {
function deleteModule(req, res) { function deleteModule(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`正在删除模块:${name}`); $.info(`正在删除模块:${name}`);
let allModules = $.read(MODULES_KEY); let allModules = $.read(MODULES_KEY);
deleteByName(allModules, name); deleteByName(allModules, name);

View File

@@ -61,7 +61,7 @@ async function updateSettings(req, res) {
export async function updateAvatar() { export async function updateAvatar() {
const settings = $.read(SETTINGS_KEY); const settings = $.read(SETTINGS_KEY);
const { githubUser: username, syncPlatform } = settings; const { githubUser: username, syncPlatform, githubProxy } = settings;
if (username) { if (username) {
if (syncPlatform === 'gitlab') { if (syncPlatform === 'gitlab') {
try { try {
@@ -92,7 +92,9 @@ export async function updateAvatar() {
try { try {
const data = await $.http const data = await $.http
.get({ .get({
url: `https://api.github.com/users/${encodeURIComponent( url: `${
githubProxy ? `${githubProxy}/` : ''
}https://api.github.com/users/${encodeURIComponent(
username, username,
)}`, )}`,
headers: { headers: {

View File

@@ -39,10 +39,8 @@ export default function register($app) {
// subscriptions API // subscriptions API
async function getFlowInfo(req, res) { async function getFlowInfo(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
let { url } = req.query; let { url } = req.query;
if (url) { if (url) {
url = decodeURIComponent(url);
$.info(`指定远程订阅 URL: ${url}`); $.info(`指定远程订阅 URL: ${url}`);
} }
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
@@ -232,6 +230,7 @@ async function getFlowInfo(req, res) {
function createSubscription(req, res) { function createSubscription(req, res) {
const sub = req.body; const sub = req.body;
delete sub.subscriptions;
$.info(`正在创建订阅: ${sub.name}`); $.info(`正在创建订阅: ${sub.name}`);
if (/\//.test(sub.name)) { if (/\//.test(sub.name)) {
failed( failed(
@@ -262,9 +261,9 @@ function createSubscription(req, res) {
function getSubscription(req, res) { function getSubscription(req, res) {
let { name } = req.params; let { name } = req.params;
let { raw } = req.query; let { raw } = req.query;
name = decodeURIComponent(name);
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);
delete sub.subscriptions;
if (sub) { if (sub) {
if (raw) { if (raw) {
res.set('content-type', 'application/json') res.set('content-type', 'application/json')
@@ -294,8 +293,8 @@ function getSubscription(req, res) {
function updateSubscription(req, res) { function updateSubscription(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name); // the original name
let sub = req.body; let sub = req.body;
delete sub.subscriptions;
const allSubs = $.read(SUBS_KEY); const allSubs = $.read(SUBS_KEY);
const oldSub = findByName(allSubs, name); const oldSub = findByName(allSubs, name);
if (oldSub) { if (oldSub) {
@@ -357,7 +356,6 @@ function updateSubscription(req, res) {
function deleteSubscription(req, res) { function deleteSubscription(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`删除订阅:${name}...`); $.info(`删除订阅:${name}...`);
// delete from subscriptions // delete from subscriptions
let allSubs = $.read(SUBS_KEY); let allSubs = $.read(SUBS_KEY);

View File

@@ -792,7 +792,6 @@ async function syncAllArtifacts(_, res) {
async function syncArtifact(req, res) { async function syncArtifact(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name);
$.info(`开始同步远程配置 ${name}...`); $.info(`开始同步远程配置 ${name}...`);
const allArtifacts = $.read(ARTIFACTS_KEY); const allArtifacts = $.read(ARTIFACTS_KEY);
const artifact = findByName(allArtifacts, name); const artifact = findByName(allArtifacts, name);

View File

@@ -17,7 +17,6 @@ export default function register($app) {
function deleteToken(req, res) { function deleteToken(req, res) {
let { token } = req.params; let { token } = req.params;
token = decodeURIComponent(token);
$.info(`正在删除:${token}`); $.info(`正在删除:${token}`);
let allTokens = $.read(TOKENS_KEY); let allTokens = $.read(TOKENS_KEY);
deleteByName(allTokens, token, 'token'); deleteByName(allTokens, token, 'token');

View File

@@ -260,37 +260,27 @@ export default async function download(
shouldCache = false; shouldCache = false;
} }
} }
if (preprocess) {
try {
const proxies = ProxyUtils.parse(body);
if (!Array.isArray(proxies) || proxies.length === 0) {
$.error(`URL ${url} 不包含有效节点, 不缓存`);
shouldCache = false;
}
} catch (e) {
$.error(
`URL ${url} 尝试解析节点失败 ${e.message ?? e}, 不缓存`,
);
shouldCache = false;
}
}
if (shouldCache) { if (shouldCache) {
resourceCache.set(id, body); resourceCache.set(id, body);
if (customCacheKey) { if (customCacheKey) {
let shouldWriteCustomCacheKey = true; $.info(
if (preprocess) { `URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
try { );
const proxies = ProxyUtils.parse(body); $.write(body, customCacheKey);
if (
!Array.isArray(proxies) ||
proxies.length === 0
) {
$.error(
`URL ${url} 不包含有效节点\n不写入自定义缓存 ${$arguments?.cacheKey}`,
);
shouldWriteCustomCacheKey = false;
}
} catch (e) {
$.error(
`URL ${url} 尝试解析节点失败 ${
e.message ?? e
}\n不写入自定义缓存 ${$arguments?.cacheKey}`,
);
shouldWriteCustomCacheKey = false;
}
}
if (shouldWriteCustomCacheKey) {
$.info(
`URL ${url}\n写入自定义缓存 ${$arguments?.cacheKey}`,
);
$.write(body, customCacheKey);
}
} }
} }

View File

@@ -230,7 +230,7 @@ export function flowTransfer(flow, unit = 'B') {
let unitIndex = unitList.indexOf(unit); let unitIndex = unitList.indexOf(unit);
return flow < 1024 || unitIndex === unitList.length - 1 return flow < 1024 || unitIndex === unitList.length - 1
? { value: flow.toFixed(1), unit: unit } ? { value: (Math.round(flow * 100) / 100).toString(), unit: unit }
: flowTransfer(flow / 1024, unitList[++unitIndex]); : flowTransfer(flow / 1024, unitList[++unitIndex]);
} }
@@ -342,6 +342,12 @@ export function normalizeFlowHeader(flowHeaders) {
) { ) {
try { try {
decodedValue = Number(decodedValue).toFixed(0); decodedValue = Number(decodedValue).toFixed(0);
if (
['expire'].includes(key) &&
decodedValue <= 0
) {
decodedValue = '';
}
} catch (e) { } catch (e) {
$.error( $.error(
`Failed to convert value for key "${key}=${encodedValue}": ${ `Failed to convert value for key "${key}=${encodedValue}": ${

View File

@@ -46,6 +46,7 @@ const ISOFlags = {
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'], '🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
'🇭🇷': ['HR', 'HRV'], '🇭🇷': ['HR', 'HRV'],
'🇭🇺': ['HU', 'HUN'], '🇭🇺': ['HU', 'HUN'],
'🇮🇶': ['IQ', 'IRQ'], // 伊拉克
'🇯🇴': ['JO', 'JOR'], '🇯🇴': ['JO', 'JOR'],
'🇯🇵': ['JP', 'JPN', 'TYO'], '🇯🇵': ['JP', 'JPN', 'TYO'],
'🇰🇪': ['KE', 'KEN'], '🇰🇪': ['KE', 'KEN'],
@@ -99,6 +100,7 @@ const ISOFlags = {
'🇸🇬': ['SG', 'SGP'], '🇸🇬': ['SG', 'SGP'],
'🇸🇮': ['SI', 'SVN'], '🇸🇮': ['SI', 'SVN'],
'🇸🇰': ['SK', 'SVK'], '🇸🇰': ['SK', 'SVK'],
'🇹🇬': ['TG', 'TGO'], // 多哥
'🇹🇭': ['TH', 'THA'], '🇹🇭': ['TH', 'THA'],
'🇹🇳': ['TN', 'TUN'], '🇹🇳': ['TN', 'TUN'],
'🇹🇷': ['TR', 'TUR'], '🇹🇷': ['TR', 'TUR'],
@@ -220,6 +222,7 @@ export function getFlag(name) {
], ],
'🇭🇷': ['Croatia', '克罗地亚', '克羅地亞'], '🇭🇷': ['Croatia', '克罗地亚', '克羅地亞'],
'🇭🇺': ['Hungary', '匈牙利'], '🇭🇺': ['Hungary', '匈牙利'],
'🇮🇶': ['Iraq', '伊拉克', '巴格达', 'Baghdad'], // 伊拉克
'🇯🇴': ['Jordan', '约旦'], '🇯🇴': ['Jordan', '约旦'],
'🇯🇵': [ '🇯🇵': [
'Japan', 'Japan',
@@ -338,6 +341,7 @@ export function getFlag(name) {
], ],
'🇸🇮': ['Slovenia', '斯洛文尼亚'], '🇸🇮': ['Slovenia', '斯洛文尼亚'],
'🇸🇰': ['Slovakia', '斯洛伐克'], '🇸🇰': ['Slovakia', '斯洛伐克'],
'🇹🇬': ['Togo', '多哥', '洛美', 'Lomé', 'Lome'], // 多哥
'🇹🇭': ['Thailand', '泰国', '泰國', '曼谷'], '🇹🇭': ['Thailand', '泰国', '泰國', '曼谷'],
'🇹🇳': ['Tunisia', '突尼斯'], '🇹🇳': ['Tunisia', '突尼斯'],
'🇹🇷': ['Turkey', '土耳其', '伊斯坦布尔', 'Istanbul'], '🇹🇷': ['Turkey', '土耳其', '伊斯坦布尔', 'Istanbul'],

View File

@@ -9,7 +9,11 @@ import { SETTINGS_KEY } from '@/constants';
export default class Gist { export default class Gist {
constructor({ token, key, syncPlatform }) { constructor({ token, key, syncPlatform }) {
const { isStash, isLoon, isShadowRocket, isQX } = ENV(); const { isStash, isLoon, isShadowRocket, isQX } = ENV();
const { defaultProxy, defaultTimeout: timeout } = $.read(SETTINGS_KEY); const {
defaultProxy,
defaultTimeout: timeout,
githubProxy,
} = $.read(SETTINGS_KEY);
let proxy = defaultProxy; let proxy = defaultProxy;
if ($.env.isNode) { if ($.env.isNode) {
proxy = proxy =
@@ -63,7 +67,9 @@ export default class Gist {
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36',
}; };
this.http = HTTP({ this.http = HTTP({
baseURL: 'https://api.github.com', baseURL: `${
githubProxy ? `${githubProxy}/` : ''
}https://api.github.com`,
headers: { headers: {
...this.headers, ...this.headers,
...(isStash && proxy ...(isStash && proxy

View File

@@ -270,7 +270,7 @@ function extractURL(url) {
let hashes = url.slice(url.indexOf('?') + 1).split('&'); let hashes = url.slice(url.indexOf('?') + 1).split('&');
for (let i = 0; i < hashes.length; i++) { for (let i = 0; i < hashes.length; i++) {
const hash = hashes[i].split('='); const hash = hashes[i].split('=');
query[hash[0]] = hash[1]; query[hash[0]] = decodeURIComponent(hash[1]);
} }
} }
return { return {
@@ -294,7 +294,7 @@ function extractPathParams(pattern, path) {
while (path[j] !== '/' && j < path.length) { while (path[j] !== '/' && j < path.length) {
val.push(path[j++]); val.push(path[j++]);
} }
params[key.join('')] = val.join(''); params[key.join('')] = decodeURIComponent(val.join(''));
} else { } else {
if (pattern[i] !== path[j]) { if (pattern[i] !== path[j]) {
return null; return null;

View File

@@ -1,38 +1,38 @@
name: Sub-Store name: Sub-Store
description: '支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *' description: "支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *"
compat_arguments: compat_arguments:
ability: http-client-policy ability: http-client-policy
cronexp: 55 23 * * * cronexp: 55 23 * * *
sync: '"Sub-Store Sync"' sync: "Sub-Store Sync"
timeout: '120' timeout: 120
engine: auto engine: auto
produce: '"# Sub-Store Produce"' produce: "# Sub-Store Produce"
produce_cronexp: 50 */6 * * * produce_cronexp: 50 */6 * * *
produce_sub: '"sub1,sub2"' produce_sub: "sub1,sub2"
produce_col: '"col1,col2"' produce_col: "col1,col2"
compat_arguments_desc: '\n1⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 ''同步'' 或 ''同步配置''\n\n3⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4⃣ timeout\n\n脚本超时, 单位为秒\n\n5⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅' compat_arguments_desc: '\n1⃣ ability\n\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n填写任意其他值关闭\n\n2⃣ cronexp\n\n同步配置定时任务\n默认为每天 23 点 55 分\n\n定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 ''同步'' 或 ''同步配置''\n\n3⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4⃣ timeout\n\n脚本超时, 单位为秒\n\n5⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存\n\n6⃣ produce\n\n自定义处理订阅的定时任务名\n一般用于定时处理耗时较长的订阅, 以更新缓存\n这样 Surge 中拉取的时候就能用到缓存, 不至于总是超时\n若设为 # 可取消此定时任务\n默认不开启\n\n7⃣ produce_cronexp\n\n配置处理订阅的定时任务\n\n默认为每 6 小时\n\n9⃣ produce_sub\n\n自定义需定时处理的单条订阅名\n多个用 , 连接\n\n🔟 produce_col\n\n自定义需定时处理的组合订阅名\n多个用 , 连接\n\n⚠ 注意: 是 名称(name) 不是 显示名称(displayName)\n如果名称需要编码, 请编码后再用 , 连接\n顺序: 并发执行单条订阅, 然后并发执行组合订阅'
scriptings: scriptings:
- http_request: - http_request:
name: Sub-Store Core name: Sub-Store Core
match: ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) match: ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info)))
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js
body_required: true body_required: true
- http_request: - http_request:
name: Sub-Store Simple name: Sub-Store Simple
match: ^https?:\/\/sub\.store match: ^https?:\/\/sub\.store
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js
body_required: true body_required: true
- schedule: - schedule:
name: '{{{sync}}}' name: "{{{sync}}}"
cron: '{{{cronexp}}}' cron: "{{{cronexp}}}"
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
- schedule: - schedule:
name: '{{{produce}}}' name: "{{{produce}}}"
cron: '{{{produce_cronexp}}}' cron: "{{{produce_cronexp}}}"
script_url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js script_url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
arguments: arguments:
_compat.$argument: '"sub={{{produce_sub}}}&col={{{produce_col}}}"' _compat.$argument: "sub={{{produce_sub}}}&col={{{produce_col}}}"
mitm: mitm:
hostnames: hostnames:
includes: includes:
- sub.store - sub.store

View File

@@ -14,7 +14,7 @@ DOMAIN,sub-store.vercel.app,PROXY
hostname=sub.store hostname=sub.store
[Script] [Script]
http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core http-request ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js, requires-body=true, timeout=120, tag=Sub-Store Core
http-request ^https?:\/\/sub\.store script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple http-request ^https?:\/\/sub\.store script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js, requires-body=true, timeout=120, tag=Sub-Store Simple
cron "55 23 * * *" script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js, timeout=120, tag=Sub-Store Sync cron "55 23 * * *" script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, timeout=120, tag=Sub-Store Sync

View File

@@ -2,6 +2,6 @@
"name": "Sub-Store", "name": "Sub-Store",
"description": "定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'", "description": "定时任务默认为每天 23 点 55 分. 定时任务指定时将订阅/文件上传到私有 Gist. 在前端, 叫做 '同步' 或 '同步配置'",
"task": [ "task": [
"55 23 * * * https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png" "55 23 * * * https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, tag=Sub-Store Sync, img-url=https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"
] ]
} }

View File

@@ -1,4 +1,4 @@
hostname=sub.store hostname=sub.store
^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) url script-analyze-echo-response https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js ^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))) url script-analyze-echo-response https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js
^https?:\/\/sub\.store url script-analyze-echo-response https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js ^https?:\/\/sub\.store url script-analyze-echo-response https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js

View File

@@ -13,8 +13,11 @@ https://xream.notion.site/Sub-Store-abe6a96944724dc6a36833d5c9ab7c87
## App 版 ## App 版
### 1. Loon ### 1. Loon
安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。 安装使用 插件 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Loon.plugin) 即可。
资源解析器中使用 [https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-parser.loon.min.js](https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-parser.loon.min.js)
### 2. Surge ### 2. Surge
#### 关于 Surge 的格外说明 #### 关于 Surge 的格外说明
@@ -29,26 +32,30 @@ Surge Mac 版如何支持 SSR, 如何去除 HTTP 传输层以支持 类似 VMess
> 最新版 Surge 已删除 `ability: http-client-policy` 参数, 模块暂不做修改, 对测落地功能无影响 > 最新版 Surge 已删除 `ability: http-client-policy` 参数, 模块暂不做修改, 对测落地功能无影响
2. 经典版, 不支持编辑参数, 固定带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule) 2. 经典版, 不支持编辑参数, 固定带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者 cname 脚本] 请使用此带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-ability.sgmodule)
3. 经典版, 不支持编辑参数, 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 3. 经典版, 不支持编辑参数, 固定不带 ability 参数版本: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule)
### 3. QX ### 3. QX
订阅 重写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet) 即可。 订阅 重写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX.snippet) 即可。
定时任务: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX-Task.json`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX-Task.json) 定时任务: [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX-Task.json`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/QX-Task.json)
### 4. Stash ### 4. Stash
安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。 安装使用 覆写 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Stash.stoverride) 即可。
### 5. Shadowrocket ### 5. Shadowrocket
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 即可。 安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Noability.sgmodule) 即可。
### 6. Egern ### 6. Egern
安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml) 即可。 安装使用 模块 [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Egern.yaml) 即可。
## 使用 Sub-Store ## 使用 Sub-Store
1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。 1. 使用 Safari 打开这个 https://sub.store 如网页正常打开并且未弹出任何错误提示,说明 Sub-Store 已经配置成功。
2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。 2. 可以把 Sub-Store 添加到主屏幕,即可获得类似于 APP 的使用体验。
3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。 3. 更详细的使用指南请参考[文档](https://www.notion.so/Sub-Store-6259586994d34c11a4ced5c406264b46)。

View File

@@ -25,13 +25,13 @@ cron:
script-providers: script-providers:
sub-store-0: sub-store-0:
url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js
interval: 86400 interval: 86400
sub-store-1: sub-store-1:
url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js
interval: 86400 interval: 86400
cron-sync-artifacts: cron-sync-artifacts:
url: https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js url: https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
interval: 86400 interval: 86400

View File

@@ -8,10 +8,10 @@
hostname = %APPEND% sub.store hostname = %APPEND% sub.store
[Script] [Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}} Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}}
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}} Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js,engine={{{engine}}} {{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}" {{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"

View File

@@ -7,7 +7,7 @@ hostname = %APPEND% sub.store
[Script] [Script]
# 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 则可以使用此脚本 # 主程序 已经去掉 Sub-Store Core 的参数 [,ability=http-client-policy] 不会爆内存,这个参数在 Surge 非常占用内存; 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 则可以使用此脚本
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js,requires-body=true,timeout=120 Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js,requires-body=true,timeout=120 Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js

View File

@@ -6,7 +6,7 @@
hostname = %APPEND% sub.store hostname = %APPEND% sub.store
[Script] [Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability=http-client-policy
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js,requires-body=true,timeout=120 Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout=120
Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js Sub-Store Sync=type=cron,cronexp=55 23 * * *,wake-system=1,timeout=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js

View File

@@ -8,10 +8,10 @@
hostname = %APPEND% sub.store hostname = %APPEND% sub.store
[Script] [Script]
Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}} Sub-Store Core=type=http-request,pattern=^https?:\/\/sub\.store\/((download)|api\/(preview|sync|(utils\/node-info))),script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout={{{timeout}}},ability="{{{ability}}}",engine={{{engine}}}
Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}} Sub-Store Simple=type=http-request,pattern=^https?:\/\/sub\.store,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true,timeout={{{timeout}}},engine={{{engine}}}
{{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js,engine={{{engine}}} {{{sync}}}=type=cron,cronexp="{{{cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
{{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://raw.githubusercontent.com/sub-store-org/Sub-Store/release/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}" {{{produce}}}=type=cron,cronexp="{{{produce_cronexp}}}",wake-system=1,timeout={{{timeout}}},script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}},argument="sub={{{produce_sub}}}&col={{{produce_col}}}"