mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d53947d820 | ||
|
|
7e75031e92 | ||
|
|
4a07c02dc1 | ||
|
|
95d6688539 | ||
|
|
a23e2ffcd6 | ||
|
|
fda1252d0e | ||
|
|
62c5c2e15b | ||
|
|
ffabcc9391 | ||
|
|
0825f15d04 | ||
|
|
fbf6b5ce6e | ||
|
|
3eb0816c88 | ||
|
|
8fc755ff02 | ||
|
|
6d3d6fa1b3 | ||
|
|
4ef4431c2c | ||
|
|
5058662651 | ||
|
|
f9d120bac3 | ||
|
|
72a445ae33 | ||
|
|
5e2a87e250 | ||
|
|
71fc9affbf | ||
|
|
6f82294c49 | ||
|
|
7c398ba51c | ||
|
|
7002eee88d | ||
|
|
bd21d58fe7 | ||
|
|
2ea46dcbf1 |
@@ -32,7 +32,7 @@ Core functionalities:
|
|||||||
|
|
||||||
example: `socks5+tls://user:pass@ip:port#name`
|
example: `socks5+tls://user:pass@ip:port#name`
|
||||||
|
|
||||||
- [x] URI(SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
- [x] URI(SOCKS, SS, SSR, VMess, VLESS, Trojan, Hysteria, Hysteria 2, TUIC v5, WireGuard)
|
||||||
- [x] Clash Proxies YAML
|
- [x] Clash Proxies YAML
|
||||||
- [x] Clash Proxy JSON(single line)
|
- [x] Clash Proxy JSON(single line)
|
||||||
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5, VLESS)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.16.32",
|
"version": "2.16.55",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
isValidPortNumber,
|
isValidPortNumber,
|
||||||
|
isValidUUID,
|
||||||
isNotBlank,
|
isNotBlank,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
getRandomPort,
|
getRandomPort,
|
||||||
@@ -21,6 +22,8 @@ import { findByName } from '@/utils/database';
|
|||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
import { getFlag, removeFlag, getISO, MMDB } from '@/utils/geo';
|
||||||
import Gist from '@/utils/gist';
|
import Gist from '@/utils/gist';
|
||||||
|
import { isPresent } from './producers/utils';
|
||||||
|
import { doh } from '@/utils/dns';
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
@@ -75,7 +78,16 @@ function parse(raw) {
|
|||||||
$.error(`Failed to parse line: ${line}`);
|
$.error(`Failed to parse line: ${line}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return proxies;
|
return proxies.filter((proxy) => {
|
||||||
|
if (['vless', 'vmess'].includes(proxy.type)) {
|
||||||
|
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
||||||
|
if (!isProxyUUIDValid) {
|
||||||
|
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
||||||
|
}
|
||||||
|
// return isProxyUUIDValid;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFn(
|
async function processFn(
|
||||||
@@ -214,10 +226,22 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter(
|
proxies = proxies.filter((proxy) => {
|
||||||
(proxy) =>
|
// 检查代理是否支持目标平台
|
||||||
!(proxy.supported && proxy.supported[targetPlatform] === false),
|
if (proxy.supported && proxy.supported[targetPlatform] === false) {
|
||||||
);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于 vless 和 vmess 代理,需要额外验证 UUID
|
||||||
|
if (['vless', 'vmess'].includes(proxy.type)) {
|
||||||
|
const isProxyUUIDValid = isValidUUID(proxy.uuid);
|
||||||
|
if (!isProxyUUIDValid)
|
||||||
|
$.error(`UUID may be invalid: ${proxy.name} ${proxy.uuid}`);
|
||||||
|
// return isProxyUUIDValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
proxies = proxies.map((proxy) => {
|
proxies = proxies.map((proxy) => {
|
||||||
proxy._resolved = proxy.resolved;
|
proxy._resolved = proxy.resolved;
|
||||||
@@ -303,6 +327,8 @@ export const ProxyUtils = {
|
|||||||
MMDB,
|
MMDB,
|
||||||
Gist,
|
Gist,
|
||||||
download,
|
download,
|
||||||
|
isValidUUID,
|
||||||
|
doh,
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@@ -400,9 +426,14 @@ function lastParse(proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
['trojan', 'tuic', 'hysteria', 'hysteria2', 'juicity'].includes(
|
[
|
||||||
proxy.type,
|
'trojan',
|
||||||
)
|
'tuic',
|
||||||
|
'hysteria',
|
||||||
|
'hysteria2',
|
||||||
|
'juicity',
|
||||||
|
'anytls',
|
||||||
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
proxy.tls = true;
|
proxy.tls = true;
|
||||||
}
|
}
|
||||||
@@ -572,6 +603,20 @@ function lastParse(proxy) {
|
|||||||
if (!proxy['tls-fingerprint'] && caStr) {
|
if (!proxy['tls-fingerprint'] && caStr) {
|
||||||
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
proxy['tls-fingerprint'] = rs.generateFingerprint(caStr);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
['shadowsocks'].includes(proxy.type) &&
|
||||||
|
isPresent(proxy, 'shadow-tls-password')
|
||||||
|
) {
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: proxy['shadow-tls-sni'],
|
||||||
|
password: proxy['shadow-tls-password'],
|
||||||
|
version: proxy['shadow-tls-version'],
|
||||||
|
};
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,46 @@ function URI_PROXY() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_SOCKS() {
|
||||||
|
const name = 'URI SOCKS Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^socks:\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
// parse url
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
let [__, type, auth, server, port, query, name] = line.match(
|
||||||
|
/^(socks)?:\/\/(?:(.*)@)?(.*?)(?::(\d+?))?(\?.*?)?(?:#(.*?))?$/,
|
||||||
|
);
|
||||||
|
if (port) {
|
||||||
|
port = parseInt(port, 10);
|
||||||
|
} else {
|
||||||
|
$.error(`port is not present in line: ${line}`);
|
||||||
|
throw new Error(`port is not present in line: ${line}`);
|
||||||
|
}
|
||||||
|
let username, password;
|
||||||
|
if (auth) {
|
||||||
|
const parsed = Base64.decode(decodeURIComponent(auth)).split(':');
|
||||||
|
username = parsed[0];
|
||||||
|
password = parsed[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
name:
|
||||||
|
name != null
|
||||||
|
? decodeURIComponent(name)
|
||||||
|
: `${type} ${server}:${port}`,
|
||||||
|
type: 'socks5',
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
// Parse SS URI format (only supports new SIP002, legacy format is depreciated).
|
||||||
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
|
||||||
function URI_SS() {
|
function URI_SS() {
|
||||||
@@ -113,6 +152,7 @@ function URI_SS() {
|
|||||||
query = parsed[2];
|
query = parsed[2];
|
||||||
}
|
}
|
||||||
content = Base64.decode(content);
|
content = Base64.decode(content);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
if (/(&|\?)v2ray-plugin=/.test(query)) {
|
||||||
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
const parsed = query.match(/(&|\?)v2ray-plugin=(.*?)(&|$)/);
|
||||||
@@ -126,8 +166,11 @@ function URI_SS() {
|
|||||||
}
|
}
|
||||||
content = `${content}${query}`;
|
content = `${content}${query}`;
|
||||||
}
|
}
|
||||||
userInfoStr = content.split('@')[0];
|
userInfoStr = content.match(/(^.*)@/)?.[1];
|
||||||
serverAndPortArray = content.match(/@([^/]*)(\/|$)/);
|
serverAndPortArray = content.match(/@([^/@]*)(\/|$)/);
|
||||||
|
} else if (content.includes('?')) {
|
||||||
|
const parsed = content.match(/(\?.*)$/);
|
||||||
|
query = parsed[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverAndPort = serverAndPortArray[1];
|
const serverAndPort = serverAndPortArray[1];
|
||||||
@@ -147,6 +190,8 @@ function URI_SS() {
|
|||||||
|
|
||||||
// handle obfs
|
// handle obfs
|
||||||
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
|
const pluginMatch = content.match(/[?&]plugin=([^&]+)/);
|
||||||
|
const shadowTlsMatch = content.match(/[?&]shadow-tls=([^&]+)/);
|
||||||
|
|
||||||
if (pluginMatch) {
|
if (pluginMatch) {
|
||||||
const pluginInfo = (
|
const pluginInfo = (
|
||||||
'plugin=' + decodeURIComponent(pluginMatch[1])
|
'plugin=' + decodeURIComponent(pluginMatch[1])
|
||||||
@@ -190,6 +235,25 @@ function URI_SS() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Shadowrocket
|
||||||
|
if (shadowTlsMatch) {
|
||||||
|
const params = JSON.parse(Base64.decode(shadowTlsMatch[1]));
|
||||||
|
const version = getIfNotBlank(params['version']);
|
||||||
|
const address = getIfNotBlank(params['address']);
|
||||||
|
const port = getIfNotBlank(params['port']);
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: getIfNotBlank(params['host']),
|
||||||
|
password: getIfNotBlank(params['password']),
|
||||||
|
version: version ? parseInt(version, 10) : undefined,
|
||||||
|
};
|
||||||
|
if (address) {
|
||||||
|
proxy.server = address;
|
||||||
|
}
|
||||||
|
if (port) {
|
||||||
|
proxy.port = parseInt(port, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
if (/(&|\?)uot=(1|true)/i.test(query)) {
|
||||||
proxy['udp-over-tcp'] = true;
|
proxy['udp-over-tcp'] = true;
|
||||||
}
|
}
|
||||||
@@ -796,8 +860,11 @@ function URI_TUIC() {
|
|||||||
const parse = (line) => {
|
const parse = (line) => {
|
||||||
line = line.split(/tuic:\/\//)[1];
|
line = line.split(/tuic:\/\//)[1];
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
let [__, uuid, password, server, ___, port, ____, addons = '', name] =
|
let [__, auth, server, port, addons = '', name] =
|
||||||
/^(.*?):(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
/^(.*?)@(.*?)(?::(\d+))?\/?(?:\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
auth = decodeURIComponent(auth);
|
||||||
|
let [uuid, ...passwordParts] = auth.split(':');
|
||||||
|
let password = passwordParts.join(':');
|
||||||
port = parseInt(`${port}`, 10);
|
port = parseInt(`${port}`, 10);
|
||||||
if (isNaN(port)) {
|
if (isNaN(port)) {
|
||||||
port = 443;
|
port = 443;
|
||||||
@@ -819,12 +886,14 @@ function URI_TUIC() {
|
|||||||
|
|
||||||
for (const addon of addons.split('&')) {
|
for (const addon of addons.split('&')) {
|
||||||
let [key, value] = addon.split('=');
|
let [key, value] = addon.split('=');
|
||||||
key = key.replace(/_/, '-');
|
key = key.replace(/_/g, '-');
|
||||||
value = decodeURIComponent(value);
|
value = decodeURIComponent(value);
|
||||||
if (['alpn'].includes(key)) {
|
if (['alpn'].includes(key)) {
|
||||||
proxy[key] = value ? value.split(',') : undefined;
|
proxy[key] = value ? value.split(',') : undefined;
|
||||||
} else if (['allow-insecure'].includes(key)) {
|
} else if (['allow-insecure'].includes(key)) {
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(value);
|
||||||
|
} else if (['fast-open'].includes(key)) {
|
||||||
|
proxy.tfo = true;
|
||||||
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
} else if (['disable-sni', 'reduce-rtt'].includes(key)) {
|
||||||
proxy[key] = /(TRUE)|1/i.test(value);
|
proxy[key] = /(TRUE)|1/i.test(value);
|
||||||
} else {
|
} else {
|
||||||
@@ -962,6 +1031,7 @@ function Clash_All() {
|
|||||||
const proxy = JSON.parse(line);
|
const proxy = JSON.parse(line);
|
||||||
if (
|
if (
|
||||||
![
|
![
|
||||||
|
'anytls',
|
||||||
'mieru',
|
'mieru',
|
||||||
'juicity',
|
'juicity',
|
||||||
'ss',
|
'ss',
|
||||||
@@ -1464,6 +1534,7 @@ function isIP(ip) {
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
URI_PROXY(),
|
URI_PROXY(),
|
||||||
|
URI_SOCKS(),
|
||||||
URI_SS(),
|
URI_SS(),
|
||||||
URI_SSR(),
|
URI_SSR(),
|
||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function Base64Encoded() {
|
|||||||
const keys = [
|
const keys = [
|
||||||
'dm1lc3M', // vmess
|
'dm1lc3M', // vmess
|
||||||
'c3NyOi8v', // ssr://
|
'c3NyOi8v', // ssr://
|
||||||
|
'c29ja3M6Ly', // socks://
|
||||||
'dHJvamFu', // trojan
|
'dHJvamFu', // trojan
|
||||||
'c3M6Ly', // ss:/
|
'c3M6Ly', // ss:/
|
||||||
'c3NkOi8v', // ssd://
|
'c3NkOi8v', // ssd://
|
||||||
@@ -62,7 +63,7 @@ function Clash() {
|
|||||||
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
// 防止 VLESS节点 reality-opts 选项中的 short-id 被解析成 Infinity
|
||||||
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
// 匹配 short-id 冒号后面的值(包含空格和引号)
|
||||||
const afterReplace = raw.replace(
|
const afterReplace = raw.replace(
|
||||||
/short-id:([ ]*[^,\n}]*)/g,
|
/short-id:([ \t]*[^#\n,}]*)/g,
|
||||||
(matched, value) => {
|
(matched, value) => {
|
||||||
const afterTrim = value.trim();
|
const afterTrim = value.trim();
|
||||||
|
|
||||||
|
|||||||
@@ -365,7 +365,9 @@ function ScriptOperator(script, targetPlatform, $arguments, source, $options) {
|
|||||||
if (output?.$file?.type === 'mihomoProfile') {
|
if (output?.$file?.type === 'mihomoProfile') {
|
||||||
try {
|
try {
|
||||||
let patch = YAML.safeLoad(script);
|
let patch = YAML.safeLoad(script);
|
||||||
if (typeof patch !== 'object') patch = {};
|
// if (typeof patch !== 'object') patch = {};
|
||||||
|
if (typeof patch !== 'object')
|
||||||
|
throw new Error('patch is not an object');
|
||||||
output.$content = ProxyUtils.yaml.safeDump(
|
output.$content = ProxyUtils.yaml.safeDump(
|
||||||
deepMerge(
|
deepMerge(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ export default function Clash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ export default function ClashMeta_Producer() {
|
|||||||
password: proxy['shadow-tls-password'],
|
password: proxy['shadow-tls-password'],
|
||||||
version: proxy['shadow-tls-version'],
|
version: proxy['shadow-tls-version'],
|
||||||
};
|
};
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +163,7 @@ export default function ClashMeta_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { isPresent } from './utils';
|
||||||
|
|
||||||
export default function Egern_Producer() {
|
export default function Egern_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies, type, opts = {}) => {
|
const produce = (proxies, type) => {
|
||||||
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
// https://egernapp.com/zh-CN/docs/configuration/proxies
|
||||||
const list = proxies
|
const list = proxies
|
||||||
.filter((proxy) => {
|
.filter((proxy) => {
|
||||||
@@ -71,6 +73,7 @@ export default function Egern_Producer() {
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((proxy) => {
|
.map((proxy) => {
|
||||||
|
const original = { ...proxy };
|
||||||
if (proxy.tls && !proxy.sni) {
|
if (proxy.tls && !proxy.sni) {
|
||||||
proxy.sni = proxy.server;
|
proxy.sni = proxy.server;
|
||||||
}
|
}
|
||||||
@@ -184,6 +187,7 @@ export default function Egern_Producer() {
|
|||||||
websocket: proxy.websocket,
|
websocket: proxy.websocket,
|
||||||
};
|
};
|
||||||
} else if (proxy.type === 'vmess') {
|
} else if (proxy.type === 'vmess') {
|
||||||
|
// Egern:传输层,支持 ws/wss/http1/http2/tls,不配置则为 tcp
|
||||||
let security = proxy.cipher;
|
let security = proxy.cipher;
|
||||||
if (
|
if (
|
||||||
security &&
|
security &&
|
||||||
@@ -212,9 +216,11 @@ export default function Egern_Producer() {
|
|||||||
};
|
};
|
||||||
} else if (proxy.network === 'http') {
|
} else if (proxy.network === 'http') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http: {
|
http1: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: proxy['http-opts']?.path,
|
path: Array.isArray(proxy['http-opts']?.path)
|
||||||
|
? proxy['http-opts']?.path[0]
|
||||||
|
: proxy['http-opts']?.path,
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@@ -225,9 +231,29 @@ export default function Egern_Producer() {
|
|||||||
skip_tls_verify: proxy['skip-cert-verify'],
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (proxy.network === 'tcp' || !proxy.network) {
|
} else if (proxy.network === 'h2') {
|
||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
[proxy.tls ? 'tls' : 'tcp']: {
|
http2: {
|
||||||
|
method: proxy['h2-opts']?.method,
|
||||||
|
path: Array.isArray(proxy['h2-opts']?.path)
|
||||||
|
? proxy['h2-opts']?.path[0]
|
||||||
|
: proxy['h2-opts']?.path,
|
||||||
|
headers: {
|
||||||
|
Host: Array.isArray(
|
||||||
|
proxy['h2-opts']?.headers?.Host,
|
||||||
|
)
|
||||||
|
? proxy['h2-opts']?.headers?.Host[0]
|
||||||
|
: proxy['h2-opts']?.headers?.Host,
|
||||||
|
},
|
||||||
|
skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
(proxy.network === 'tcp' || !proxy.network) &&
|
||||||
|
proxy.tls
|
||||||
|
) {
|
||||||
|
proxy.transport = {
|
||||||
|
tls: {
|
||||||
sni: proxy.tls ? proxy.sni : undefined,
|
sni: proxy.tls ? proxy.sni : undefined,
|
||||||
skip_tls_verify: proxy.tls
|
skip_tls_verify: proxy.tls
|
||||||
? proxy['skip-cert-verify']
|
? proxy['skip-cert-verify']
|
||||||
@@ -269,7 +295,9 @@ export default function Egern_Producer() {
|
|||||||
proxy.transport = {
|
proxy.transport = {
|
||||||
http: {
|
http: {
|
||||||
method: proxy['http-opts']?.method,
|
method: proxy['http-opts']?.method,
|
||||||
path: proxy['http-opts']?.path,
|
path: Array.isArray(proxy['http-opts']?.path)
|
||||||
|
? proxy['http-opts']?.path[0]
|
||||||
|
: proxy['http-opts']?.path,
|
||||||
headers: {
|
headers: {
|
||||||
Host: Array.isArray(
|
Host: Array.isArray(
|
||||||
proxy['http-opts']?.headers?.Host,
|
proxy['http-opts']?.headers?.Host,
|
||||||
@@ -307,6 +335,39 @@ export default function Egern_Producer() {
|
|||||||
// skip_tls_verify: proxy['skip-cert-verify'],
|
// skip_tls_verify: proxy['skip-cert-verify'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'http',
|
||||||
|
'socks5',
|
||||||
|
'ss',
|
||||||
|
'trojan',
|
||||||
|
'vless',
|
||||||
|
'vmess',
|
||||||
|
].includes(original.type)
|
||||||
|
) {
|
||||||
|
if (isPresent(original, 'shadow-tls-password')) {
|
||||||
|
if (original['shadow-tls-version'] != 3)
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${original['shadow-tls-version']} is not supported`,
|
||||||
|
);
|
||||||
|
proxy.shadow_tls = {
|
||||||
|
password: original['shadow-tls-password'],
|
||||||
|
sni: original['shadow-tls-sni'],
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
['shadow-tls'].includes(original.plugin) &&
|
||||||
|
original['plugin-opts']
|
||||||
|
) {
|
||||||
|
if (original['plugin-opts'].version != 3)
|
||||||
|
throw new Error(
|
||||||
|
`shadow-tls version ${original['plugin-opts'].version} is not supported`,
|
||||||
|
);
|
||||||
|
proxy.shadow_tls = {
|
||||||
|
password: original['plugin-opts'].password,
|
||||||
|
sni: original['plugin-opts'].host,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
|||||||
@@ -341,10 +341,9 @@ function vmess(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
const ip_version =
|
|
||||||
ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,10 +415,9 @@ function vless(proxy) {
|
|||||||
// udp
|
// udp
|
||||||
if (proxy.udp) {
|
if (proxy.udp) {
|
||||||
result.append(`,udp=true`);
|
result.append(`,udp=true`);
|
||||||
const ip_version =
|
|
||||||
ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
|
||||||
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
|
||||||
}
|
}
|
||||||
|
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
|
||||||
|
result.appendIfPresent(`,ip-mode=${ip_version}`, 'ip-version');
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function Shadowrocket_Producer() {
|
|||||||
if (opts['include-unsupported-proxy']) return true;
|
if (opts['include-unsupported-proxy']) return true;
|
||||||
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
if (proxy.type === 'snell' && String(proxy.version) === '4') {
|
||||||
return false;
|
return false;
|
||||||
} else if (['mieru'].includes(proxy.type)) {
|
} else if (['mieru', 'anytls'].includes(proxy.type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -110,6 +110,21 @@ export default function Shadowrocket_Producer() {
|
|||||||
proxy.servername = proxy.sni;
|
proxy.servername = proxy.sni;
|
||||||
delete proxy.sni;
|
delete proxy.sni;
|
||||||
}
|
}
|
||||||
|
} else if (proxy.type === 'ss') {
|
||||||
|
if (
|
||||||
|
isPresent(proxy, 'shadow-tls-password') &&
|
||||||
|
!isPresent(proxy, 'plugin')
|
||||||
|
) {
|
||||||
|
proxy.plugin = 'shadow-tls';
|
||||||
|
proxy['plugin-opts'] = {
|
||||||
|
host: proxy['shadow-tls-sni'],
|
||||||
|
password: proxy['shadow-tls-password'],
|
||||||
|
version: proxy['shadow-tls-version'],
|
||||||
|
};
|
||||||
|
delete proxy['shadow-tls-password'];
|
||||||
|
delete proxy['shadow-tls-sni'];
|
||||||
|
delete proxy['shadow-tls-version'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -163,6 +178,7 @@ export default function Shadowrocket_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -641,6 +641,23 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
|
const anytlsParser = (proxy = {}) => {
|
||||||
|
const parsedProxy = {
|
||||||
|
tag: proxy.name,
|
||||||
|
type: 'anytls',
|
||||||
|
server: proxy.server,
|
||||||
|
server_port: parseInt(`${proxy.port}`, 10),
|
||||||
|
password: proxy.password,
|
||||||
|
tls: { enabled: true, server_name: proxy.server, insecure: false },
|
||||||
|
};
|
||||||
|
if (/^\d+$/.test(proxy['idle-session-check-interval']))
|
||||||
|
parsedProxy.idle_session_check_interval = `${proxy['idle-session-check-interval']}s`;
|
||||||
|
if (/^\d+$/.test(proxy['idle-session-timeout']))
|
||||||
|
parsedProxy.idle_session_timeout = `${proxy['idle-session-timeout']}s`;
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
|
tlsParser(proxy, parsedProxy);
|
||||||
|
return parsedProxy;
|
||||||
|
};
|
||||||
|
|
||||||
const wireguardParser = (proxy = {}) => {
|
const wireguardParser = (proxy = {}) => {
|
||||||
const local_address = ['ip', 'ipv6']
|
const local_address = ['ip', 'ipv6']
|
||||||
@@ -829,6 +846,9 @@ export default function singbox_Producer() {
|
|||||||
case 'wireguard':
|
case 'wireguard':
|
||||||
list.push(wireguardParser(proxy));
|
list.push(wireguardParser(proxy));
|
||||||
break;
|
break;
|
||||||
|
case 'anytls':
|
||||||
|
list.push(anytlsParser(proxy));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
`Platform sing-box does not support proxy type: ${proxy.type}`,
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ export default function Stash_Producer() {
|
|||||||
'hysteria',
|
'hysteria',
|
||||||
'hysteria2',
|
'hysteria2',
|
||||||
'juicity',
|
'juicity',
|
||||||
|
'anytls',
|
||||||
].includes(proxy.type)
|
].includes(proxy.type)
|
||||||
) {
|
) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export default function URI_Producer() {
|
|||||||
proxy.server = `[${proxy.server}]`;
|
proxy.server = `[${proxy.server}]`;
|
||||||
}
|
}
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
|
case 'socks5':
|
||||||
|
result = `socks://${encodeURIComponent(
|
||||||
|
Base64.encode(`${proxy.username}:${proxy.password}`),
|
||||||
|
)}@${proxy.server}:${proxy.port}#${proxy.name}`;
|
||||||
|
break;
|
||||||
case 'ss':
|
case 'ss':
|
||||||
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
const userinfo = `${proxy.cipher}:${proxy.password}`;
|
||||||
result = `ss://${
|
result = `ss://${
|
||||||
@@ -516,10 +521,13 @@ export default function URI_Producer() {
|
|||||||
['disable-sni', 'reduce-rtt'].includes(key) &&
|
['disable-sni', 'reduce-rtt'].includes(key) &&
|
||||||
proxy[key]
|
proxy[key]
|
||||||
) {
|
) {
|
||||||
tuicParams.push(`${i}=1`);
|
tuicParams.push(`${i.replace(/-/g, '_')}=1`);
|
||||||
} else if (proxy[key]) {
|
} else if (proxy[key]) {
|
||||||
tuicParams.push(
|
tuicParams.push(
|
||||||
`${i}=${encodeURIComponent(proxy[key])}`,
|
`${i.replace(
|
||||||
|
/-/g,
|
||||||
|
'_',
|
||||||
|
)}=${encodeURIComponent(proxy[key])}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,18 @@ function getCollection(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_collection_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(collection));
|
.send(JSON.stringify(collection));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -209,7 +209,18 @@ function getWholeFile(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_file_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(file));
|
.send(JSON.stringify(file));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,7 +27,18 @@ export default function register($app) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
'attachment; filename="sub-store.json"',
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_data_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(
|
.send(
|
||||||
$.env.isNode
|
$.env.isNode
|
||||||
|
|||||||
@@ -15,46 +15,48 @@ export default function register($app) {
|
|||||||
async function previewFile(req, res) {
|
async function previewFile(req, res) {
|
||||||
try {
|
try {
|
||||||
const file = req.body;
|
const file = req.body;
|
||||||
let content;
|
let content = '';
|
||||||
if (
|
if (file.type !== 'mihomoProfile') {
|
||||||
file.source === 'local' &&
|
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
|
||||||
) {
|
|
||||||
content = file.content;
|
|
||||||
} else {
|
|
||||||
const errors = {};
|
|
||||||
content = await Promise.all(
|
|
||||||
file.url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)
|
|
||||||
.map(async (url) => {
|
|
||||||
try {
|
|
||||||
return await download(url, file.ua);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!file.ignoreFailedRemoteFile &&
|
file.source === 'local' &&
|
||||||
Object.keys(errors).length > 0
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
content = file.content;
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
} else {
|
||||||
', ',
|
const errors = {};
|
||||||
)} 发生错误, 请查看日志`,
|
content = await Promise.all(
|
||||||
|
file.url
|
||||||
|
.split(/[\r\n]+/)
|
||||||
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(url, file.ua);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
if (file.mergeSources === 'localFirst') {
|
if (
|
||||||
content.unshift(file.content);
|
!file.ignoreFailedRemoteFile &&
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
Object.keys(errors).length > 0
|
||||||
content.push(file.content);
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
|
errors,
|
||||||
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (file.mergeSources === 'localFirst') {
|
||||||
|
content.unshift(file.content);
|
||||||
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
|
content.push(file.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// parse proxies
|
// parse proxies
|
||||||
|
|||||||
@@ -264,7 +264,18 @@ function getSubscription(req, res) {
|
|||||||
res.set('content-type', 'application/json')
|
res.set('content-type', 'application/json')
|
||||||
.set(
|
.set(
|
||||||
'content-disposition',
|
'content-disposition',
|
||||||
`attachment; filename="${encodeURIComponent(name)}.json"`,
|
`attachment; filename="${encodeURIComponent(
|
||||||
|
`sub-store_subscription_${name}_${new Date()
|
||||||
|
.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
})
|
||||||
|
.replace(/\D/g, '')}.json`,
|
||||||
|
)}"`,
|
||||||
)
|
)
|
||||||
.send(JSON.stringify(sub));
|
.send(JSON.stringify(sub));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -410,105 +410,117 @@ async function produceArtifact({
|
|||||||
const allFiles = $.read(FILES_KEY);
|
const allFiles = $.read(FILES_KEY);
|
||||||
const file = findByName(allFiles, name);
|
const file = findByName(allFiles, name);
|
||||||
if (!file) throw new Error(`找不到文件 ${name}`);
|
if (!file) throw new Error(`找不到文件 ${name}`);
|
||||||
let raw;
|
let raw = '';
|
||||||
if (content && !['localFirst', 'remoteFirst'].includes(mergeSources)) {
|
console.log(file);
|
||||||
raw = content;
|
if (file.type !== 'mihomoProfile') {
|
||||||
} else if (url) {
|
|
||||||
const errors = {};
|
|
||||||
raw = await Promise.all(
|
|
||||||
url
|
|
||||||
.split(/[\r\n]+/)
|
|
||||||
.map((i) => i.trim())
|
|
||||||
.filter((i) => i.length)
|
|
||||||
.map(async (url) => {
|
|
||||||
try {
|
|
||||||
return await download(
|
|
||||||
url,
|
|
||||||
ua || file.ua,
|
|
||||||
undefined,
|
|
||||||
file.proxy || proxy,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
noCache,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
|
||||||
if (
|
if (
|
||||||
ignoreFailedRemoteFile != null &&
|
content &&
|
||||||
ignoreFailedRemoteFile !== ''
|
!['localFirst', 'remoteFirst'].includes(mergeSources)
|
||||||
) {
|
) {
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
raw = content;
|
||||||
}
|
} else if (url) {
|
||||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
const errors = {};
|
||||||
throw new Error(
|
raw = await Promise.all(
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
url
|
||||||
', ',
|
.split(/[\r\n]+/)
|
||||||
)} 发生错误, 请查看日志`,
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
if (mergeSources === 'localFirst') {
|
if (
|
||||||
raw.unshift(content);
|
ignoreFailedRemoteFile != null &&
|
||||||
} else if (mergeSources === 'remoteFirst') {
|
ignoreFailedRemoteFile !== ''
|
||||||
raw.push(content);
|
) {
|
||||||
}
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
} else if (
|
}
|
||||||
file.source === 'local' &&
|
if (
|
||||||
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
!fileIgnoreFailedRemoteFile &&
|
||||||
) {
|
Object.keys(errors).length > 0
|
||||||
raw = file.content;
|
) {
|
||||||
} else {
|
throw new Error(
|
||||||
const errors = {};
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
raw = await Promise.all(
|
errors,
|
||||||
file.url
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
.split(/[\r\n]+/)
|
);
|
||||||
.map((i) => i.trim())
|
}
|
||||||
.filter((i) => i.length)
|
if (mergeSources === 'localFirst') {
|
||||||
.map(async (url) => {
|
raw.unshift(content);
|
||||||
try {
|
} else if (mergeSources === 'remoteFirst') {
|
||||||
return await download(
|
raw.push(content);
|
||||||
url,
|
}
|
||||||
ua || file.ua,
|
} else if (
|
||||||
undefined,
|
file.source === 'local' &&
|
||||||
file.proxy || proxy,
|
!['localFirst', 'remoteFirst'].includes(file.mergeSources)
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
noCache,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
errors[url] = err;
|
|
||||||
$.error(
|
|
||||||
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
|
||||||
if (
|
|
||||||
ignoreFailedRemoteFile != null &&
|
|
||||||
ignoreFailedRemoteFile !== ''
|
|
||||||
) {
|
) {
|
||||||
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
raw = file.content;
|
||||||
}
|
} else {
|
||||||
if (!fileIgnoreFailedRemoteFile && Object.keys(errors).length > 0) {
|
const errors = {};
|
||||||
throw new Error(
|
raw = await Promise.all(
|
||||||
`文件 ${file.name} 的远程文件 ${Object.keys(errors).join(
|
file.url
|
||||||
', ',
|
.split(/[\r\n]+/)
|
||||||
)} 发生错误, 请查看日志`,
|
.map((i) => i.trim())
|
||||||
|
.filter((i) => i.length)
|
||||||
|
.map(async (url) => {
|
||||||
|
try {
|
||||||
|
return await download(
|
||||||
|
url,
|
||||||
|
ua || file.ua,
|
||||||
|
undefined,
|
||||||
|
file.proxy || proxy,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
noCache,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errors[url] = err;
|
||||||
|
$.error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${url} 发生错误: ${err}`,
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
let fileIgnoreFailedRemoteFile = file.ignoreFailedRemoteFile;
|
||||||
if (file.mergeSources === 'localFirst') {
|
if (
|
||||||
raw.unshift(file.content);
|
ignoreFailedRemoteFile != null &&
|
||||||
} else if (file.mergeSources === 'remoteFirst') {
|
ignoreFailedRemoteFile !== ''
|
||||||
raw.push(file.content);
|
) {
|
||||||
|
fileIgnoreFailedRemoteFile = ignoreFailedRemoteFile;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!fileIgnoreFailedRemoteFile &&
|
||||||
|
Object.keys(errors).length > 0
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`文件 ${file.name} 的远程文件 ${Object.keys(
|
||||||
|
errors,
|
||||||
|
).join(', ')} 发生错误, 请查看日志`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (file.mergeSources === 'localFirst') {
|
||||||
|
raw.unshift(file.content);
|
||||||
|
} else if (file.mergeSources === 'remoteFirst') {
|
||||||
|
raw.push(file.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
const files = (Array.isArray(raw) ? raw : [raw]).flat();
|
||||||
|
|||||||
@@ -117,7 +117,17 @@ function numberToString(value) {
|
|||||||
: BigInt(value).toString();
|
: BigInt(value).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidUUID(uuid) {
|
||||||
|
return (
|
||||||
|
typeof uuid === 'string' &&
|
||||||
|
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
||||||
|
uuid,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
isValidUUID,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
isIPv4,
|
isIPv4,
|
||||||
isIPv6,
|
isIPv6,
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
// 16. `sing-box` 支持使用 `_network` 来设置 `network`, 例如 `tcp`, `udp`
|
||||||
|
|
||||||
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
|
||||||
|
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件
|
||||||
|
// const fs = eval(`require("fs")`)
|
||||||
|
// // const path = eval(`require("path")`)
|
||||||
|
// fs.writeFileSync('/tmp/1.txt', $content, "utf8");
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@@ -69,6 +73,7 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// Gist, // Gist 类
|
// Gist, // Gist 类
|
||||||
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
// download, // 内部的下载方法, 见 backend/src/utils/download.js
|
||||||
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
// MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269
|
||||||
|
// isValidUUID, // 辅助判断是否为有效的 UUID
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
// 如果只是为了快速修改或者筛选 可以参考 脚本操作支持节点快捷脚本 https://t.me/zhetengsha/970 和 脚本筛选支持节点快捷脚本 https://t.me/zhetengsha/1009
|
||||||
|
|||||||
Reference in New Issue
Block a user