mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d255390d48 | ||
|
|
72c7f4333a | ||
|
|
f35837ff9f | ||
|
|
c2c39c5de6 | ||
|
|
87a4b14ae2 | ||
|
|
ff1dacda87 | ||
|
|
9426f128c4 | ||
|
|
ebc7173c95 | ||
|
|
dd4e0cef68 |
@@ -32,14 +32,14 @@ Core functionalities:
|
||||
|
||||
example: `socks5+tls://user:pass@ip:port#name`
|
||||
|
||||
- [x] URI(SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] URI(AnyTLS, SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||
- [x] Clash Proxies YAML
|
||||
- [x] Clash Proxy JSON(single line)
|
||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, SOCKS5-TLS, WireGuard, VLESS, Hysteria 2)
|
||||
- [x] Surge (Direct, 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] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru)
|
||||
- [x] Clash.Meta (Direct, SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria 2, TUIC, SSH, mieru, AnyTLS)
|
||||
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC, Juicity, SSH)
|
||||
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.16.60",
|
||||
"version": "2.17.4",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -185,6 +185,7 @@ async function processFn(
|
||||
}
|
||||
} else {
|
||||
script = content;
|
||||
$arguments = item.args.arguments || {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -699,6 +699,52 @@ function URI_VLESS() {
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
function URI_AnyTLS() {
|
||||
const name = 'URI AnyTLS Parser';
|
||||
const test = (line) => {
|
||||
return /^anytls:\/\//.test(line);
|
||||
};
|
||||
const parse = (line) => {
|
||||
line = line.split(/anytls:\/\//)[1];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let [__, password, server, port, addons = '', name] =
|
||||
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||
password = decodeURIComponent(password);
|
||||
port = parseInt(`${port}`, 10);
|
||||
if (isNaN(port)) {
|
||||
port = 443;
|
||||
}
|
||||
password = decodeURIComponent(password);
|
||||
if (name != null) {
|
||||
name = decodeURIComponent(name);
|
||||
}
|
||||
name = name ?? `AnyTLS ${server}:${port}`;
|
||||
|
||||
const proxy = {
|
||||
type: 'anytls',
|
||||
name,
|
||||
server,
|
||||
port,
|
||||
password,
|
||||
};
|
||||
|
||||
for (const addon of addons.split('&')) {
|
||||
let [key, value] = addon.split('=');
|
||||
key = key.replace(/_/g, '-');
|
||||
value = decodeURIComponent(value);
|
||||
if (['alpn'].includes(key)) {
|
||||
proxy[key] = value ? value.split(',') : undefined;
|
||||
} else if (['insecure'].includes(key)) {
|
||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||
} else {
|
||||
proxy[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
};
|
||||
return { name, test, parse };
|
||||
}
|
||||
function URI_Hysteria2() {
|
||||
const name = 'URI Hysteria2 Parser';
|
||||
const test = (line) => {
|
||||
@@ -1544,6 +1590,7 @@ export default [
|
||||
URI_Hysteria(),
|
||||
URI_Hysteria2(),
|
||||
URI_Trojan(),
|
||||
URI_AnyTLS(),
|
||||
Clash_All(),
|
||||
Surge_Direct(),
|
||||
Surge_SSH(),
|
||||
|
||||
@@ -246,7 +246,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
||||
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(""); }
|
||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
|
||||
@@ -243,7 +243,7 @@ block_quic = comma "block-quic" equals match:[^,]+ { proxy["block-quic"] = match
|
||||
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(""); }
|
||||
shadow_tls_password = comma "shadow-tls-password" equals match:[^,]+ { proxy["shadow-tls-password"] = match.join("").replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
|
||||
token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); }
|
||||
alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); }
|
||||
uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); }
|
||||
|
||||
@@ -379,19 +379,22 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
throw new Error('patch is not an object');
|
||||
output.$content = ProxyUtils.yaml.safeDump(
|
||||
deepMerge(
|
||||
config || {
|
||||
proxies: await produceArtifact({
|
||||
type:
|
||||
output?.$file?.sourceType ||
|
||||
'collection',
|
||||
name: output?.$file?.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
config ||
|
||||
(output?.$file?.sourceType === 'none'
|
||||
? {}
|
||||
: {
|
||||
proxies: await produceArtifact({
|
||||
type:
|
||||
output?.$file?.sourceType ||
|
||||
'collection',
|
||||
name: output?.$file?.sourceName,
|
||||
platform: 'mihomo',
|
||||
produceType: 'internal',
|
||||
produceOpts: {
|
||||
'delete-underscore-fields': true,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
patch,
|
||||
),
|
||||
);
|
||||
@@ -430,7 +433,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
console.log(e.message ?? e);
|
||||
}
|
||||
}
|
||||
$content = ProxyUtils.yaml.safeDump(await main(config || {
|
||||
$content = ProxyUtils.yaml.safeDump(await main(config || ($file.sourceType === 'none' ? {} : {
|
||||
proxies: await produceArtifact({
|
||||
type: $file.sourceType || 'collection',
|
||||
name: $file.sourceName,
|
||||
@@ -440,7 +443,7 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
||||
'delete-underscore-fields': true
|
||||
}
|
||||
}),
|
||||
}))
|
||||
})))
|
||||
}
|
||||
} else {
|
||||
${script}
|
||||
@@ -846,7 +849,12 @@ function UselessFilter() {
|
||||
}
|
||||
|
||||
// filter by regions
|
||||
function RegionFilter(regions) {
|
||||
function RegionFilter(input) {
|
||||
let regions = input?.value || input;
|
||||
if (!Array.isArray(regions)) {
|
||||
regions = [];
|
||||
}
|
||||
const keep = input?.keep ?? true;
|
||||
const REGION_MAP = {
|
||||
HK: '🇭🇰',
|
||||
TW: '🇹🇼',
|
||||
@@ -863,7 +871,8 @@ function RegionFilter(regions) {
|
||||
// this would be high memory usage
|
||||
return proxies.map((proxy) => {
|
||||
const flag = getFlag(proxy.name);
|
||||
return regions.some((r) => REGION_MAP[r] === flag);
|
||||
const selected = regions.some((r) => REGION_MAP[r] === flag);
|
||||
return keep ? selected : !selected;
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -895,11 +904,19 @@ function buildRegex(str, ...options) {
|
||||
}
|
||||
|
||||
// filter by proxy types
|
||||
function TypeFilter(types) {
|
||||
function TypeFilter(input) {
|
||||
let types = input?.value || input;
|
||||
if (!Array.isArray(types)) {
|
||||
types = [];
|
||||
}
|
||||
const keep = input?.keep ?? true;
|
||||
return {
|
||||
name: 'Type Filter',
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => types.some((t) => proxy.type === t));
|
||||
return proxies.map((proxy) => {
|
||||
const selected = types.some((t) => proxy.type === t);
|
||||
return keep ? selected : !selected;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,10 +123,10 @@ export default function Egern_Producer() {
|
||||
proxy.udp || proxy.udp_relay || proxy.udp_relay,
|
||||
next_hop: proxy.next_hop,
|
||||
};
|
||||
if (proxy.plugin === 'obfs') {
|
||||
proxy.obfs = proxy['plugin-opts'].mode;
|
||||
proxy.obfs_host = proxy['plugin-opts'].host;
|
||||
proxy.obfs_uri = proxy['plugin-opts'].path;
|
||||
if (original.plugin === 'obfs') {
|
||||
proxy.obfs = original['plugin-opts'].mode;
|
||||
proxy.obfs_host = original['plugin-opts'].host;
|
||||
proxy.obfs_uri = original['plugin-opts'].path;
|
||||
}
|
||||
} else if (proxy.type === 'hysteria2') {
|
||||
proxy = {
|
||||
@@ -144,9 +144,12 @@ export default function Egern_Producer() {
|
||||
port_hopping: proxy.ports,
|
||||
port_hopping_interval: proxy['hop-interval'],
|
||||
};
|
||||
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
|
||||
if (
|
||||
original['obfs-password'] &&
|
||||
original.obfs == 'salamander'
|
||||
) {
|
||||
proxy.obfs = 'salamander';
|
||||
proxy.obfs_password = proxy['obfs-password'];
|
||||
proxy.obfs_password = original['obfs-password'];
|
||||
}
|
||||
} else if (proxy.type === 'tuic') {
|
||||
proxy = {
|
||||
|
||||
@@ -433,6 +433,9 @@ function ssh(proxy) {
|
||||
return result.toString();
|
||||
}
|
||||
function http(proxy) {
|
||||
if (proxy.headers && Object.keys(proxy.headers).length > 0) {
|
||||
throw new Error(`headers is unsupported`);
|
||||
}
|
||||
const result = new Result(proxy);
|
||||
const type = proxy.tls ? 'https' : 'http';
|
||||
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
|
||||
|
||||
@@ -141,10 +141,19 @@ function mihomo(proxy, type, opts) {
|
||||
dns: {
|
||||
enable: true,
|
||||
ipv6,
|
||||
nameserver: [
|
||||
'https://223.6.6.6/dns-query',
|
||||
'https://120.53.53.53/dns-query',
|
||||
],
|
||||
'default-nameserver': opts?.defaultNameserver ||
|
||||
proxy._defaultNameserver || [
|
||||
'180.76.76.76',
|
||||
'52.80.52.52',
|
||||
'119.28.28.28',
|
||||
'223.6.6.6',
|
||||
],
|
||||
nameserver: opts?.nameserver ||
|
||||
proxy._nameserver || [
|
||||
'https://doh.pub/dns-query',
|
||||
'https://dns.alidns.com/dns-query',
|
||||
'https://doh-pure.onedns.net/dns-query',
|
||||
],
|
||||
},
|
||||
proxies: [
|
||||
{
|
||||
|
||||
@@ -493,6 +493,7 @@ export default function URI_Producer() {
|
||||
'password',
|
||||
'server',
|
||||
'port',
|
||||
'tls',
|
||||
].includes(key)
|
||||
) {
|
||||
const i = key.replace(/-/, '_');
|
||||
@@ -542,6 +543,50 @@ export default function URI_Producer() {
|
||||
)}`;
|
||||
}
|
||||
break;
|
||||
case 'anytls':
|
||||
let anytlsParams = [];
|
||||
Object.keys(proxy).forEach((key) => {
|
||||
if (
|
||||
![
|
||||
'name',
|
||||
'type',
|
||||
'password',
|
||||
'server',
|
||||
'port',
|
||||
'tls',
|
||||
].includes(key)
|
||||
) {
|
||||
const i = key.replace(/-/, '_');
|
||||
if (['alpn'].includes(key)) {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(
|
||||
`${i}=${encodeURIComponent(
|
||||
Array.isArray(proxy[key])
|
||||
? proxy[key][0]
|
||||
: proxy[key],
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} else if (['skip-cert-verify'].includes(key)) {
|
||||
if (proxy[key]) {
|
||||
anytlsParams.push(`insecure=1`);
|
||||
}
|
||||
} else if (proxy[key]) {
|
||||
anytlsParams.push(
|
||||
`${i.replace(/-/g, '_')}=${encodeURIComponent(
|
||||
proxy[key],
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result = `anytls://${encodeURIComponent(proxy.password)}@${
|
||||
proxy.server
|
||||
}:${proxy.port}/?${anytlsParams.join('&')}#${encodeURIComponent(
|
||||
proxy.name,
|
||||
)}`;
|
||||
break;
|
||||
case 'wireguard':
|
||||
let wireguardParams = [];
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class ResourceCache {
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
_cleanup() {
|
||||
_cleanup(prefix, expires) {
|
||||
// clear obsolete cached resource
|
||||
let clear = false;
|
||||
Object.entries(this.resourceCache).forEach((entry) => {
|
||||
@@ -35,7 +35,11 @@ class ResourceCache {
|
||||
$.delete(`#${id}`);
|
||||
clear = true;
|
||||
}
|
||||
if (new Date().getTime() - updated.time > this.expires) {
|
||||
if (
|
||||
new Date().getTime() - updated.time >
|
||||
(expires ?? this.expires) ||
|
||||
(prefix && id.startsWith(prefix))
|
||||
) {
|
||||
delete this.resourceCache[id];
|
||||
clear = true;
|
||||
}
|
||||
@@ -52,10 +56,15 @@ class ResourceCache {
|
||||
$.write(JSON.stringify(this.resourceCache), SCRIPT_RESOURCE_CACHE_KEY);
|
||||
}
|
||||
|
||||
get(id) {
|
||||
get(id, expires, remove) {
|
||||
const updated = this.resourceCache[id] && this.resourceCache[id].time;
|
||||
if (updated && new Date().getTime() - updated <= this.expires) {
|
||||
return this.resourceCache[id].data;
|
||||
if (updated) {
|
||||
if (new Date().getTime() - updated <= (expires ?? this.expires))
|
||||
return this.resourceCache[id].data;
|
||||
if (remove) {
|
||||
delete this.resourceCache[id];
|
||||
this._persist();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,32 @@ function operator(proxies = [], targetPlatform, context) {
|
||||
// scriptResourceCache 缓存
|
||||
// 可参考 https://t.me/zhetengsha/1003
|
||||
// const cache = scriptResourceCache
|
||||
// cache.set(id, data)
|
||||
// cache.get(id)
|
||||
// 设置
|
||||
// cache.set('a:1', 1)
|
||||
// cache.set('a:2', 2)
|
||||
// 获取
|
||||
// cache.get('a:1')
|
||||
// 支持第二个参数: 自定义过期时间
|
||||
// 支持第三个参数: 是否删除过期项
|
||||
// cache.get('a:2', 1000, true)
|
||||
|
||||
// 清理
|
||||
// cache._cleanup()
|
||||
// 支持第一个参数: 匹配前缀的项也一起删除
|
||||
// 支持第二个参数: 自定义过期时间
|
||||
// cache._cleanup('a:', 1000)
|
||||
|
||||
// 关于缓存时长
|
||||
|
||||
// 拉取 Sub-Store 订阅时, 会自动拉取远程订阅
|
||||
|
||||
// 远程订阅缓存是 1 小时, 缓存的唯一 key 为 url+ user agent. 可通过前端的刷新按钮刷新缓存. 或使用参数 noCache 来禁用缓存. 例: 内部配置订阅链接时使用 http://a.com#noCache, 外部使用 sub-store 链接时使用 https://sub.store/download/1?noCache=true
|
||||
|
||||
// 当使用相关脚本时, 若在对应的脚本中使用参数开启缓存, 可设置持久化缓存 sub-store-csr-expiration-time 的值来自定义默认缓存时长, 默认为 172800000 (48 * 3600 * 1000, 即 48 小时)
|
||||
|
||||
// 🎈Loon 可在插件中设置
|
||||
|
||||
// 其他平台同理, 持久化缓存数据在 JSON 里
|
||||
|
||||
// ProxyUtils 为节点处理工具
|
||||
// 可参考 https://t.me/zhetengsha/1066
|
||||
|
||||
Reference in New Issue
Block a user