mirror of
https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 00:52:40 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c93de48ab | ||
|
|
274aa50373 | ||
|
|
e24de8d0b6 | ||
|
|
93a5ce6c3b | ||
|
|
cb66c8daa2 | ||
|
|
f4cdc953e6 | ||
|
|
2a1c2eb9df | ||
|
|
6217c2e5cd | ||
|
|
f90d9c2fd1 | ||
|
|
3e952e9e88 | ||
|
|
a81b55f752 | ||
|
|
33652af516 | ||
|
|
2bca669930 | ||
|
|
f1bf0e1e8d | ||
|
|
16b9cd9aaf | ||
|
|
32eb069ab2 | ||
|
|
4c9f8011c7 | ||
|
|
bd26b0a561 | ||
|
|
958d1e52c8 | ||
|
|
e7a2e60963 | ||
|
|
fa6a274f79 | ||
|
|
e40b3f88d5 | ||
|
|
163ad9ee09 | ||
|
|
abb6f2dec1 | ||
|
|
56870bbd5f | ||
|
|
efbc6ecd84 | ||
|
|
c27c589024 | ||
|
|
0efed4f1a0 | ||
|
|
e3a514d1fb | ||
|
|
64478c7a27 | ||
|
|
dc8f19f350 | ||
|
|
b4ccfc7e07 | ||
|
|
3f1940630a | ||
|
|
5a0bdb1276 | ||
|
|
a1b86e26a2 | ||
|
|
6ec8c29f6a | ||
|
|
bbb9602f9f | ||
|
|
6db6153672 | ||
|
|
b66189948a | ||
|
|
2611dccc73 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -21,11 +21,11 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "20"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
cd backend && pnpm i
|
cd backend && pnpm i --no-frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sub-store",
|
"name": "sub-store",
|
||||||
"version": "2.14.254",
|
"version": "2.14.286",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import $ from '@/core/app';
|
|||||||
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
import { FILES_KEY, MODULES_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
import { getFlag, getISO } from '@/utils/geo';
|
||||||
|
|
||||||
function preprocess(raw) {
|
function preprocess(raw) {
|
||||||
for (const processor of PROXY_PREPROCESSORS) {
|
for (const processor of PROXY_PREPROCESSORS) {
|
||||||
@@ -159,7 +160,7 @@ async function processFn(proxies, operators = [], targetPlatform, source) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.info(
|
$.log(
|
||||||
`Applying "${item.type}" with arguments:\n >>> ${
|
`Applying "${item.type}" with arguments:\n >>> ${
|
||||||
JSON.stringify(item.args, null, 2) || 'None'
|
JSON.stringify(item.args, null, 2) || 'None'
|
||||||
}`,
|
}`,
|
||||||
@@ -186,6 +187,10 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
throw new Error(`Target platform: ${targetPlatform} is not supported!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sni_off_supported = /Surge|SurgeMac|Shadowrocket/i.test(
|
||||||
|
targetPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
// filter unsupported proxies
|
// filter unsupported proxies
|
||||||
proxies = proxies.filter(
|
proxies = proxies.filter(
|
||||||
(proxy) =>
|
(proxy) =>
|
||||||
@@ -196,10 +201,22 @@ function produce(proxies, targetPlatform, type, opts = {}) {
|
|||||||
if (!isNotBlank(proxy.name)) {
|
if (!isNotBlank(proxy.name)) {
|
||||||
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||||
}
|
}
|
||||||
|
if (proxy['disable-sni']) {
|
||||||
|
if (sni_off_supported) {
|
||||||
|
proxy.sni = 'off';
|
||||||
|
} else if (!['tuic'].includes(proxy.type)) {
|
||||||
|
$.error(
|
||||||
|
`Target platform ${targetPlatform} does not support sni off. Proxy's fields (sni, tls-fingerprint and skip-cert-verify) will be modified.`,
|
||||||
|
);
|
||||||
|
proxy.sni = '';
|
||||||
|
proxy['skip-cert-verify'] = true;
|
||||||
|
delete proxy['tls-fingerprint'];
|
||||||
|
}
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
});
|
});
|
||||||
|
|
||||||
$.info(`Producing proxies for target: ${targetPlatform}`);
|
$.log(`Producing proxies for target: ${targetPlatform}`);
|
||||||
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
if (typeof producer.type === 'undefined' || producer.type === 'SINGLE') {
|
||||||
let localPort = 10000;
|
let localPort = 10000;
|
||||||
const list = proxies
|
const list = proxies
|
||||||
@@ -242,6 +259,8 @@ export const ProxyUtils = {
|
|||||||
isIPv6,
|
isIPv6,
|
||||||
isIP,
|
isIP,
|
||||||
yaml: YAML,
|
yaml: YAML,
|
||||||
|
getFlag,
|
||||||
|
getISO,
|
||||||
};
|
};
|
||||||
|
|
||||||
function tryParse(parser, line) {
|
function tryParse(parser, line) {
|
||||||
@@ -396,6 +415,9 @@ function lastParse(proxy) {
|
|||||||
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (['', 'off'].includes(proxy.sni)) {
|
||||||
|
proxy['disable-sni'] = true;
|
||||||
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ function URI_SS() {
|
|||||||
/\d+/,
|
/\d+/,
|
||||||
)?.[0];
|
)?.[0];
|
||||||
|
|
||||||
const userInfo = userInfoStr.split(':');
|
const userInfo = userInfoStr.match(/(^.*?):(.*$)/);
|
||||||
proxy.cipher = userInfo[0];
|
proxy.cipher = userInfo[1];
|
||||||
proxy.password = userInfo[1];
|
proxy.password = userInfo[2];
|
||||||
|
|
||||||
// handle obfs
|
// handle obfs
|
||||||
const idx = content.indexOf('?plugin=');
|
const idx = content.indexOf('?plugin=');
|
||||||
@@ -547,6 +547,7 @@ function URI_Hysteria2() {
|
|||||||
proxy.obfs = params.obfs;
|
proxy.obfs = params.obfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy.ports = params.mport;
|
||||||
proxy['obfs-password'] = params['obfs-password'];
|
proxy['obfs-password'] = params['obfs-password'];
|
||||||
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
proxy['skip-cert-verify'] = /(TRUE)|1/i.test(params.insecure);
|
||||||
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
proxy.tfo = /(TRUE)|1/i.test(params.fastopen);
|
||||||
@@ -673,6 +674,89 @@ function URI_TUIC() {
|
|||||||
};
|
};
|
||||||
return { name, test, parse };
|
return { name, test, parse };
|
||||||
}
|
}
|
||||||
|
function URI_WireGuard() {
|
||||||
|
const name = 'URI WireGuard Parser';
|
||||||
|
const test = (line) => {
|
||||||
|
return /^(wireguard|wg):\/\//.test(line);
|
||||||
|
};
|
||||||
|
const parse = (line) => {
|
||||||
|
line = line.split(/(wireguard|wg):\/\//)[2];
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
let [
|
||||||
|
__,
|
||||||
|
___,
|
||||||
|
privateKey,
|
||||||
|
server,
|
||||||
|
____,
|
||||||
|
port,
|
||||||
|
_____,
|
||||||
|
addons = '',
|
||||||
|
name,
|
||||||
|
] = /^((.*?)@)?(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line);
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
|
port = parseInt(`${port}`, 10);
|
||||||
|
if (isNaN(port)) {
|
||||||
|
port = 51820;
|
||||||
|
}
|
||||||
|
privateKey = decodeURIComponent(privateKey);
|
||||||
|
if (name != null) {
|
||||||
|
name = decodeURIComponent(name);
|
||||||
|
}
|
||||||
|
name = name ?? `WireGuard ${server}:${port}`;
|
||||||
|
const proxy = {
|
||||||
|
type: 'wireguard',
|
||||||
|
name,
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
'private-key': privateKey,
|
||||||
|
udp: true,
|
||||||
|
};
|
||||||
|
for (const addon of addons.split('&')) {
|
||||||
|
let [key, value] = addon.split('=');
|
||||||
|
key = key.replace(/_/, '-');
|
||||||
|
value = decodeURIComponent(value);
|
||||||
|
if (['reserved'].includes(key)) {
|
||||||
|
const parsed = value
|
||||||
|
.split(',')
|
||||||
|
.map((i) => parseInt(i.trim(), 10))
|
||||||
|
.filter((i) => Number.isInteger(i));
|
||||||
|
if (parsed.length === 3) {
|
||||||
|
proxy[key] = parsed;
|
||||||
|
}
|
||||||
|
} else if (['address', 'ip'].includes(key)) {
|
||||||
|
value.split(',').map((i) => {
|
||||||
|
const ip = i
|
||||||
|
.trim()
|
||||||
|
.replace(/\/\d+$/, '')
|
||||||
|
.replace(/^\[/, '')
|
||||||
|
.replace(/\]$/, '');
|
||||||
|
if (isIPv4(ip)) {
|
||||||
|
proxy.ip = ip;
|
||||||
|
} else if (isIPv6(ip)) {
|
||||||
|
proxy.ipv6 = ip;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (['mtu'].includes(key)) {
|
||||||
|
const parsed = parseInt(value.trim(), 10);
|
||||||
|
if (Number.isInteger(parsed)) {
|
||||||
|
proxy[key] = parsed;
|
||||||
|
}
|
||||||
|
} else if (/publickey/i.test(key)) {
|
||||||
|
proxy['public-key'] = value;
|
||||||
|
} else if (/privatekey/i.test(key)) {
|
||||||
|
proxy['private-key'] = value;
|
||||||
|
} else if (['udp'].includes(key)) {
|
||||||
|
proxy[key] = /(TRUE)|1/i.test(value);
|
||||||
|
} else if (!['flag'].includes(key)) {
|
||||||
|
proxy[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
return { name, test, parse };
|
||||||
|
}
|
||||||
|
|
||||||
// Trojan URI format
|
// Trojan URI format
|
||||||
function URI_Trojan() {
|
function URI_Trojan() {
|
||||||
@@ -750,6 +834,9 @@ function Clash_All() {
|
|||||||
if (proxy.fingerprint) {
|
if (proxy.fingerprint) {
|
||||||
proxy['tls-fingerprint'] = proxy.fingerprint;
|
proxy['tls-fingerprint'] = proxy.fingerprint;
|
||||||
}
|
}
|
||||||
|
if (proxy['dialer-proxy']) {
|
||||||
|
proxy['underlying-proxy'] = proxy['dialer-proxy'];
|
||||||
|
}
|
||||||
|
|
||||||
if (proxy['benchmark-url']) {
|
if (proxy['benchmark-url']) {
|
||||||
proxy['test-url'] = proxy['benchmark-url'];
|
proxy['test-url'] = proxy['benchmark-url'];
|
||||||
@@ -1192,6 +1279,7 @@ export default [
|
|||||||
URI_VMess(),
|
URI_VMess(),
|
||||||
URI_VLESS(),
|
URI_VLESS(),
|
||||||
URI_TUIC(),
|
URI_TUIC(),
|
||||||
|
URI_WireGuard(),
|
||||||
URI_Hysteria(),
|
URI_Hysteria(),
|
||||||
URI_Hysteria2(),
|
URI_Hysteria2(),
|
||||||
URI_Trojan(),
|
URI_Trojan(),
|
||||||
|
|||||||
@@ -177,7 +177,13 @@ username = & {
|
|||||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||||
|
|
||||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
sni = comma "sni" equals sni:("off"/domain) {
|
||||||
|
if (sni === "off") {
|
||||||
|
proxy["disable-sni"] = true;
|
||||||
|
} else {
|
||||||
|
proxy.sni = sni;
|
||||||
|
}
|
||||||
|
}
|
||||||
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_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,13 @@ username = & {
|
|||||||
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
password = comma match:[^,]+ { proxy.password = match.join(""); }
|
||||||
|
|
||||||
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
tls = comma "tls" equals flag:bool { proxy.tls = flag; }
|
||||||
sni = comma "sni" equals sni:domain { proxy.sni = sni; }
|
sni = comma "sni" equals sni:("off"/domain) {
|
||||||
|
if (sni === "off") {
|
||||||
|
proxy["disable-sni"] = true;
|
||||||
|
} else {
|
||||||
|
proxy.sni = sni;
|
||||||
|
}
|
||||||
|
}
|
||||||
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_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ function Base64Encoded() {
|
|||||||
'aHR0c', // htt
|
'aHR0c', // htt
|
||||||
'dmxlc3M=', // vless
|
'dmxlc3M=', // vless
|
||||||
'aHlzdGVyaWEy', // hysteria2
|
'aHlzdGVyaWEy', // hysteria2
|
||||||
|
'd2lyZWd1YXJkOi8v', // wireguard://
|
||||||
|
'd2c6Ly8=', // wg://
|
||||||
|
'dHVpYzovLw==', // tuic://
|
||||||
];
|
];
|
||||||
|
|
||||||
const test = function (raw) {
|
const test = function (raw) {
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ const DOMAIN_RESOLVERS = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
const answers = resp.body.split(';').map((i) => i.split(',')[0]);
|
||||||
if (answers.length === 0) {
|
if (answers.length === 0 || String(answers) === '0') {
|
||||||
throw new Error('No answers');
|
throw new Error('No answers');
|
||||||
}
|
}
|
||||||
const result = answers[answers.length - 1];
|
const result = answers[answers.length - 1];
|
||||||
@@ -550,13 +550,25 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
|
|||||||
results[p.server],
|
results[p.server],
|
||||||
);
|
);
|
||||||
if (server && port) {
|
if (server && port) {
|
||||||
|
p._domain = p.server;
|
||||||
p.server = server;
|
p.server = server;
|
||||||
p.port = port;
|
p.port = port;
|
||||||
p.resolved = true;
|
p.resolved = true;
|
||||||
|
p._IPv4 = p.server;
|
||||||
|
if (!isIP(p._IP)) {
|
||||||
|
p._IP = p.server;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.resolved = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
p._domain = p.server;
|
||||||
p.server = results[p.server];
|
p.server = results[p.server];
|
||||||
p.resolved = true;
|
p.resolved = true;
|
||||||
|
p[`_${type}`] = p.server;
|
||||||
|
if (!isIP(p._IP)) {
|
||||||
|
p._IP = p.server;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.resolved = false;
|
p.resolved = false;
|
||||||
@@ -617,6 +629,8 @@ function RegionFilter(regions) {
|
|||||||
SG: '🇸🇬',
|
SG: '🇸🇬',
|
||||||
JP: '🇯🇵',
|
JP: '🇯🇵',
|
||||||
UK: '🇬🇧',
|
UK: '🇬🇧',
|
||||||
|
DE: '🇩🇪',
|
||||||
|
KR: '🇰🇷',
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
name: 'Region Filter',
|
name: 'Region Filter',
|
||||||
|
|||||||
@@ -144,12 +144,25 @@ export default function Clash_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -160,11 +160,24 @@ export default function ClashMeta_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import singbox_Producer from './sing-box';
|
|||||||
|
|
||||||
function JSON_Producer() {
|
function JSON_Producer() {
|
||||||
const type = 'ALL';
|
const type = 'ALL';
|
||||||
const produce = (proxies) => JSON.stringify(proxies, null, 2);
|
const produce = (proxies, type) =>
|
||||||
|
type === 'internal' ? proxies : JSON.stringify(proxies, null, 2);
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,11 +163,24 @@ export default function ShadowRocket_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import ClashMeta_Producer from './clashmeta';
|
import ClashMeta_Producer from './clashmeta';
|
||||||
import $ from '@/core/app';
|
import $ from '@/core/app';
|
||||||
|
|
||||||
|
const detourParser = (proxy, parsedProxy) => {
|
||||||
|
if (proxy['dialer-proxy']) parsedProxy.detour = proxy['dialer-proxy'];
|
||||||
|
};
|
||||||
const tfoParser = (proxy, parsedProxy) => {
|
const tfoParser = (proxy, parsedProxy) => {
|
||||||
parsedProxy.tcp_fast_open = false;
|
parsedProxy.tcp_fast_open = false;
|
||||||
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
if (proxy.tfo) parsedProxy.tcp_fast_open = true;
|
||||||
@@ -249,6 +252,7 @@ const sshParser = (proxy = {}) => {
|
|||||||
parsedProxy.host_key_algorithms = proxy['host-key-algorithms'];
|
parsedProxy.host_key_algorithms = proxy['host-key-algorithms'];
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -274,6 +278,7 @@ const httpParser = (proxy = {}) => {
|
|||||||
}
|
}
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -295,6 +300,7 @@ const socks5Parser = (proxy = {}) => {
|
|||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,6 +332,7 @@ const shadowTLSParser = (proxy = {}) => {
|
|||||||
throw '端口值非法';
|
throw '端口值非法';
|
||||||
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
|
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
|
||||||
tfoParser(proxy, stPart);
|
tfoParser(proxy, stPart);
|
||||||
|
detourParser(proxy, stPart);
|
||||||
smuxParser(proxy.smux, ssPart);
|
smuxParser(proxy.smux, ssPart);
|
||||||
return { type: 'ss-with-st', ssPart, stPart };
|
return { type: 'ss-with-st', ssPart, stPart };
|
||||||
};
|
};
|
||||||
@@ -344,6 +351,7 @@ const ssParser = (proxy = {}) => {
|
|||||||
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
if (proxy['udp-over-tcp']) parsedProxy.udp_over_tcp = true;
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
if (proxy.plugin) {
|
if (proxy.plugin) {
|
||||||
const optArr = [];
|
const optArr = [];
|
||||||
@@ -421,6 +429,7 @@ const ssrParser = (proxy = {}) => {
|
|||||||
parsedProxy.protocol_param = proxy['protocol-param'];
|
parsedProxy.protocol_param = proxy['protocol-param'];
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -457,6 +466,7 @@ const vmessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -479,6 +489,7 @@ const vlessParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
if (proxy.network === 'grpc') grpcParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -499,6 +510,7 @@ const trojanParser = (proxy = {}) => {
|
|||||||
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
|
||||||
|
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -545,6 +557,7 @@ const hysteriaParser = (proxy = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -569,6 +582,7 @@ const hysteria2Parser = (proxy = {}) => {
|
|||||||
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
if (!parsedProxy.obfs.type) delete parsedProxy.obfs;
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -597,6 +611,7 @@ const tuic5Parser = (proxy = {}) => {
|
|||||||
if (proxy['heartbeat-interval'])
|
if (proxy['heartbeat-interval'])
|
||||||
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
parsedProxy.heartbeat = `${proxy['heartbeat-interval']}ms`;
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
tlsParser(proxy, parsedProxy);
|
tlsParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
@@ -622,7 +637,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
throw 'invalid port';
|
throw 'invalid port';
|
||||||
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
|
||||||
if (typeof proxy.reserved === 'string') {
|
if (typeof proxy.reserved === 'string') {
|
||||||
parsedProxy.reserved.push(proxy.reserved);
|
parsedProxy.reserved = proxy.reserved;
|
||||||
} else if (Array.isArray(proxy.reserved)) {
|
} else if (Array.isArray(proxy.reserved)) {
|
||||||
for (const r of proxy.reserved) parsedProxy.reserved.push(r);
|
for (const r of proxy.reserved) parsedProxy.reserved.push(r);
|
||||||
} else {
|
} else {
|
||||||
@@ -650,6 +665,7 @@ const wireguardParser = (proxy = {}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tfoParser(proxy, parsedProxy);
|
tfoParser(proxy, parsedProxy);
|
||||||
|
detourParser(proxy, parsedProxy);
|
||||||
smuxParser(proxy.smux, parsedProxy);
|
smuxParser(proxy.smux, parsedProxy);
|
||||||
return parsedProxy;
|
return parsedProxy;
|
||||||
};
|
};
|
||||||
@@ -789,7 +805,10 @@ export default function singbox_Producer() {
|
|||||||
$.error(e.message ?? e);
|
$.error(e.message ?? e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return type === 'internal' ? list : JSON.stringify(list, null, 2);
|
|
||||||
|
return type === 'internal'
|
||||||
|
? list
|
||||||
|
: JSON.stringify({ outbounds: list }, null, 2);
|
||||||
};
|
};
|
||||||
return { type, produce };
|
return { type, produce };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,12 @@ export default function Stash_Producer() {
|
|||||||
proxy.fingerprint = proxy['tls-fingerprint'];
|
proxy.fingerprint = proxy['tls-fingerprint'];
|
||||||
}
|
}
|
||||||
delete proxy['tls-fingerprint'];
|
delete proxy['tls-fingerprint'];
|
||||||
|
|
||||||
|
if (proxy['underlying-proxy']) {
|
||||||
|
proxy['dialer-proxy'] = proxy['underlying-proxy'];
|
||||||
|
}
|
||||||
|
delete proxy['underlying-proxy'];
|
||||||
|
|
||||||
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
if (isPresent(proxy, 'tls') && typeof proxy.tls !== 'boolean') {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -257,6 +263,13 @@ export default function Stash_Producer() {
|
|||||||
|
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
['grpc'].includes(proxy.network) &&
|
['grpc'].includes(proxy.network) &&
|
||||||
proxy[`${proxy.network}-opts`]
|
proxy[`${proxy.network}-opts`]
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ export default function URI_Producer() {
|
|||||||
let result = '';
|
let result = '';
|
||||||
delete proxy.subName;
|
delete proxy.subName;
|
||||||
delete proxy.collectionName;
|
delete proxy.collectionName;
|
||||||
|
delete proxy.id;
|
||||||
|
delete proxy.resolved;
|
||||||
|
for (const key in proxy) {
|
||||||
|
if (proxy[key] == null || /^_/i.test(key)) {
|
||||||
|
delete proxy[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
if (['trojan', 'tuic', 'hysteria', 'hysteria2'].includes(proxy.type)) {
|
||||||
delete proxy.tls;
|
delete proxy.tls;
|
||||||
}
|
}
|
||||||
@@ -274,6 +281,9 @@ export default function URI_Producer() {
|
|||||||
`sni=${encodeURIComponent(proxy.sni)}`,
|
`sni=${encodeURIComponent(proxy.sni)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (proxy.ports) {
|
||||||
|
hysteria2params.push(`mport=${proxy.ports}`);
|
||||||
|
}
|
||||||
if (proxy['tls-fingerprint']) {
|
if (proxy['tls-fingerprint']) {
|
||||||
hysteria2params.push(
|
hysteria2params.push(
|
||||||
`pinSHA256=${encodeURIComponent(
|
`pinSHA256=${encodeURIComponent(
|
||||||
@@ -401,8 +411,51 @@ export default function URI_Producer() {
|
|||||||
}?${tuicParams.join('&')}#${encodeURIComponent(
|
}?${tuicParams.join('&')}#${encodeURIComponent(
|
||||||
proxy.name,
|
proxy.name,
|
||||||
)}`;
|
)}`;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case 'wireguard':
|
||||||
|
let wireguardParams = [];
|
||||||
|
|
||||||
|
Object.keys(proxy).forEach((key) => {
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'server',
|
||||||
|
'port',
|
||||||
|
'ip',
|
||||||
|
'ipv6',
|
||||||
|
'private-key',
|
||||||
|
].includes(key)
|
||||||
|
) {
|
||||||
|
if (['public-key'].includes(key)) {
|
||||||
|
wireguardParams.push(`publickey=${proxy[key]}`);
|
||||||
|
} else if (['udp'].includes(key)) {
|
||||||
|
if (proxy[key]) {
|
||||||
|
wireguardParams.push(`${key}=1`);
|
||||||
|
}
|
||||||
|
} else if (proxy[key]) {
|
||||||
|
wireguardParams.push(
|
||||||
|
`${key}=${encodeURIComponent(proxy[key])}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (proxy.ip && proxy.ipv6) {
|
||||||
|
wireguardParams.push(
|
||||||
|
`address=${proxy.ip}/32,${proxy.ipv6}/128`,
|
||||||
|
);
|
||||||
|
} else if (proxy.ip) {
|
||||||
|
wireguardParams.push(`address=${proxy.ip}/32`);
|
||||||
|
} else if (proxy.ipv6) {
|
||||||
|
wireguardParams.push(`address=${proxy.ipv6}/128`);
|
||||||
|
}
|
||||||
|
result = `wireguard://${encodeURIComponent(
|
||||||
|
proxy['private-key'],
|
||||||
|
)}@${proxy.server}:${proxy.port}/?${wireguardParams.join(
|
||||||
|
'&',
|
||||||
|
)}#${encodeURIComponent(proxy.name)}`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const RULE_TYPES_MAPPING = [
|
|||||||
[/^PROTOCOL$/, 'PROTOCOL'],
|
[/^PROTOCOL$/, 'PROTOCOL'],
|
||||||
[/^IP-CIDR$/i, 'IP-CIDR'],
|
[/^IP-CIDR$/i, 'IP-CIDR'],
|
||||||
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/, 'IP-CIDR6'],
|
[/^(IP-CIDR6|ip6-cidr|IP6-CIDR)$/, 'IP-CIDR6'],
|
||||||
|
[/^GEOIP$/i, 'GEOIP'],
|
||||||
|
[/^GEOSITE$/i, 'GEOSITE'],
|
||||||
];
|
];
|
||||||
|
|
||||||
function AllRuleParser() {
|
function AllRuleParser() {
|
||||||
@@ -37,8 +39,7 @@ function AllRuleParser() {
|
|||||||
content: params[1],
|
content: params[1],
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
rule.type === 'IP-CIDR' ||
|
['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)
|
||||||
rule.type === 'IP-CIDR6'
|
|
||||||
) {
|
) {
|
||||||
rule.options = params.slice(2);
|
rule.options = params.slice(2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ function QXFilter() {
|
|||||||
'SRC-IP',
|
'SRC-IP',
|
||||||
'IN-PORT',
|
'IN-PORT',
|
||||||
'PROTOCOL',
|
'PROTOCOL',
|
||||||
|
'GEOSITE',
|
||||||
|
'GEOIP',
|
||||||
];
|
];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
|
|
||||||
@@ -29,6 +31,8 @@ function QXFilter() {
|
|||||||
function SurgeRuleSet() {
|
function SurgeRuleSet() {
|
||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
|
const UNSUPPORTED = ['GEOSITE', 'GEOIP'];
|
||||||
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
let output = `${rule.type},${rule.content}`;
|
let output = `${rule.type},${rule.content}`;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
||||||
output +=
|
output +=
|
||||||
@@ -43,7 +47,7 @@ function LoonRules() {
|
|||||||
const type = 'SINGLE';
|
const type = 'SINGLE';
|
||||||
const func = (rule) => {
|
const func = (rule) => {
|
||||||
// skip unsupported rules
|
// skip unsupported rules
|
||||||
const UNSUPPORTED = ['DEST-PORT', 'SRC-IP', 'IN-PORT', 'PROTOCOL'];
|
const UNSUPPORTED = ['SRC-IP', 'GEOSITE', 'GEOIP'];
|
||||||
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
if (UNSUPPORTED.indexOf(rule.type) !== -1) return null;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type) && rule.options) {
|
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type) && rule.options) {
|
||||||
// Loon only supports the no-resolve option
|
// Loon only supports the no-resolve option
|
||||||
@@ -69,7 +73,7 @@ function ClashRuleProvider() {
|
|||||||
let output = `${TRANSFORM[rule.type] || rule.type},${
|
let output = `${TRANSFORM[rule.type] || rule.type},${
|
||||||
rule.content
|
rule.content
|
||||||
}`;
|
}`;
|
||||||
if (['IP-CIDR', 'IP-CIDR6'].includes(rule.type)) {
|
if (['IP-CIDR', 'IP-CIDR6', 'GEOIP'].includes(rule.type)) {
|
||||||
if (rule.options) {
|
if (rule.options) {
|
||||||
// Clash only supports the no-resolve option
|
// Clash only supports the no-resolve option
|
||||||
rule.options = rule.options.filter((option) =>
|
rule.options = rule.options.filter((option) =>
|
||||||
|
|||||||
@@ -128,10 +128,19 @@ async function doSync() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getPlatformFromHeaders } from '@/utils/platform';
|
import { getPlatformFromHeaders } from '@/utils/user-agent';
|
||||||
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
|
||||||
import { findByName } from '@/utils/database';
|
import { findByName } from '@/utils/database';
|
||||||
import { getFlowHeaders } from '@/utils/flow';
|
import { getFlowHeaders } from '@/utils/flow';
|
||||||
@@ -6,10 +6,29 @@ import $ from '@/core/app';
|
|||||||
import { failed } from '@/restful/response';
|
import { failed } from '@/restful/response';
|
||||||
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
|
||||||
import { produceArtifact } from '@/restful/sync';
|
import { produceArtifact } from '@/restful/sync';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { isIPv4, isIPv6 } from '@/utils';
|
||||||
|
import { getISO } from '@/utils/geo';
|
||||||
|
import env from '@/utils/env';
|
||||||
|
|
||||||
export default function register($app) {
|
export default function register($app) {
|
||||||
$app.get('/download/collection/:name', downloadCollection);
|
$app.get('/download/collection/:name', downloadCollection);
|
||||||
$app.get('/download/:name', downloadSubscription);
|
$app.get('/download/:name', downloadSubscription);
|
||||||
|
$app.get(
|
||||||
|
'/download/collection/:name/api/v1/server/details',
|
||||||
|
async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha';
|
||||||
|
await downloadCollection(req, res);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
$app.get('/download/:name/api/v1/server/details', async (req, res) => {
|
||||||
|
req.query.platform = 'JSON';
|
||||||
|
req.query.produceType = 'internal';
|
||||||
|
req.query.resultFormat = 'nezha';
|
||||||
|
await downloadSubscription(req, res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadSubscription(req, res) {
|
async function downloadSubscription(req, res) {
|
||||||
@@ -28,6 +47,7 @@ async function downloadSubscription(req, res) {
|
|||||||
ignoreFailedRemoteSub,
|
ignoreFailedRemoteSub,
|
||||||
produceType,
|
produceType,
|
||||||
includeUnsupportedProxy,
|
includeUnsupportedProxy,
|
||||||
|
resultFormat,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
if (url) {
|
if (url) {
|
||||||
url = decodeURIComponent(url);
|
url = decodeURIComponent(url);
|
||||||
@@ -62,7 +82,7 @@ async function downloadSubscription(req, res) {
|
|||||||
const sub = findByName(allSubs, name);
|
const sub = findByName(allSubs, name);
|
||||||
if (sub) {
|
if (sub) {
|
||||||
try {
|
try {
|
||||||
const output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'subscription',
|
type: 'subscription',
|
||||||
name,
|
name,
|
||||||
platform,
|
platform,
|
||||||
@@ -79,8 +99,7 @@ async function downloadSubscription(req, res) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
sub.source !== 'local' ||
|
sub.source !== 'local' ||
|
||||||
['localFirst', 'remoteFirst'].includes(sub.mergeSources) ||
|
['localFirst', 'remoteFirst'].includes(sub.mergeSources)
|
||||||
url
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
url = `${url || sub.url}`
|
url = `${url || sub.url}`
|
||||||
@@ -113,7 +132,7 @@ async function downloadSubscription(req, res) {
|
|||||||
// forward flow headers
|
// forward flow headers
|
||||||
const flowInfo = await getFlowHeaders(
|
const flowInfo = await getFlowHeaders(
|
||||||
url,
|
url,
|
||||||
undefined,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
);
|
);
|
||||||
@@ -134,6 +153,9 @@ async function downloadSubscription(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
|
if (resultFormat === 'nezha') {
|
||||||
|
output = nezhaTransform(output);
|
||||||
|
}
|
||||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -181,8 +203,12 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
$.info(`正在下载组合订阅:${name}`);
|
$.info(`正在下载组合订阅:${name}`);
|
||||||
|
|
||||||
let { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy } =
|
let {
|
||||||
req.query;
|
ignoreFailedRemoteSub,
|
||||||
|
produceType,
|
||||||
|
includeUnsupportedProxy,
|
||||||
|
resultFormat,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
|
||||||
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
|
||||||
@@ -200,7 +226,7 @@ async function downloadCollection(req, res) {
|
|||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
try {
|
try {
|
||||||
const output = await produceArtifact({
|
let output = await produceArtifact({
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
name,
|
name,
|
||||||
platform,
|
platform,
|
||||||
@@ -250,7 +276,7 @@ async function downloadCollection(req, res) {
|
|||||||
if (!$arguments.noFlow) {
|
if (!$arguments.noFlow) {
|
||||||
const flowInfo = await getFlowHeaders(
|
const flowInfo = await getFlowHeaders(
|
||||||
url,
|
url,
|
||||||
undefined,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
);
|
);
|
||||||
@@ -272,6 +298,9 @@ async function downloadCollection(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
|
if (resultFormat === 'nezha') {
|
||||||
|
output = nezhaTransform(output);
|
||||||
|
}
|
||||||
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
res.set('Content-Type', 'application/json;charset=utf-8').send(
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
@@ -308,3 +337,66 @@ async function downloadCollection(req, res) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nezhaTransform(output) {
|
||||||
|
const result = {
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
result: [],
|
||||||
|
};
|
||||||
|
output.map((proxy, index) => {
|
||||||
|
// 如果节点上有数据 就取节点上的数据
|
||||||
|
let CountryCode = proxy._geo?.countryCode || proxy._geo?.country;
|
||||||
|
// 简单判断下
|
||||||
|
if (!/^[a-z]{2}$/i.test(CountryCode)) {
|
||||||
|
CountryCode = getISO(proxy.name);
|
||||||
|
}
|
||||||
|
// 简单判断下
|
||||||
|
if (/^[a-z]{2}$/i.test(CountryCode)) {
|
||||||
|
// 如果节点上有数据 就取节点上的数据
|
||||||
|
let time = proxy._unavailable ? 0 : Date.now();
|
||||||
|
result.result.push({
|
||||||
|
id: index,
|
||||||
|
name: proxy.name,
|
||||||
|
tag: `${proxy._tag ?? ''}`,
|
||||||
|
last_active: time,
|
||||||
|
// 暂时不用处理 现在 VPings App 端的接口支持域名查询
|
||||||
|
// 其他场景使用 自己在 Sub-Store 加一步域名解析
|
||||||
|
valid_ip: proxy._IP || proxy.server,
|
||||||
|
ipv4: proxy._IPv4 || proxy.server,
|
||||||
|
ipv6: proxy._IPv6 || (isIPv6(proxy.server) ? proxy.server : ''),
|
||||||
|
host: {
|
||||||
|
Platform: 'Sub-Store',
|
||||||
|
PlatformVersion: env.version,
|
||||||
|
CPU: [],
|
||||||
|
MemTotal: 1024,
|
||||||
|
DiskTotal: 1024,
|
||||||
|
SwapTotal: 1024,
|
||||||
|
Arch: '',
|
||||||
|
Virtualization: '',
|
||||||
|
BootTime: time,
|
||||||
|
CountryCode, // 目前需要
|
||||||
|
Version: '',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
CPU: 0,
|
||||||
|
MemUsed: 0,
|
||||||
|
SwapUsed: 0,
|
||||||
|
DiskUsed: 0,
|
||||||
|
NetInTransfer: 0,
|
||||||
|
NetOutTransfer: 0,
|
||||||
|
NetInSpeed: 0,
|
||||||
|
NetOutSpeed: 0,
|
||||||
|
Uptime: parseInt(proxy._uptime ?? index, 10),
|
||||||
|
Load1: 0,
|
||||||
|
Load5: 0,
|
||||||
|
Load15: 0,
|
||||||
|
TcpConnCount: 0,
|
||||||
|
UdpConnCount: 0,
|
||||||
|
ProcessCount: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(result, null, 2);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ENV } from '@/vendor/open-api';
|
|||||||
import { failed, success } from '@/restful/response';
|
import { failed, success } from '@/restful/response';
|
||||||
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
import { updateArtifactStore, updateAvatar } from '@/restful/settings';
|
||||||
import resourceCache from '@/utils/resource-cache';
|
import resourceCache from '@/utils/resource-cache';
|
||||||
|
import scriptResourceCache from '@/utils/script-resource-cache';
|
||||||
|
import headersResourceCache from '@/utils/headers-resource-cache';
|
||||||
import {
|
import {
|
||||||
GIST_BACKUP_FILE_NAME,
|
GIST_BACKUP_FILE_NAME,
|
||||||
GIST_BACKUP_KEY,
|
GIST_BACKUP_KEY,
|
||||||
@@ -73,6 +75,8 @@ async function refresh(_, res) {
|
|||||||
|
|
||||||
// 2. clear resource cache
|
// 2. clear resource cache
|
||||||
resourceCache.revokeAll();
|
resourceCache.revokeAll();
|
||||||
|
scriptResourceCache.revokeAll();
|
||||||
|
headersResourceCache.revokeAll();
|
||||||
success(res);
|
success(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,11 +157,14 @@ async function gistBackup(req, res) {
|
|||||||
}
|
}
|
||||||
success(res);
|
success(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
$.error(
|
||||||
|
`Failed to ${action} gist data.\nReason: ${err.message ?? err}`,
|
||||||
|
);
|
||||||
failed(
|
failed(
|
||||||
res,
|
res,
|
||||||
new InternalServerError(
|
new InternalServerError(
|
||||||
'BACKUP_FAILED',
|
'BACKUP_FAILED',
|
||||||
`Failed to ${action} data to gist!`,
|
`Failed to ${action} gist data!`,
|
||||||
`Reason: ${err.message ?? err}`,
|
`Reason: ${err.message ?? err}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -253,11 +253,7 @@ async function compareCollection(req, res) {
|
|||||||
errors[name] = err;
|
errors[name] = err;
|
||||||
|
|
||||||
$.error(
|
$.error(
|
||||||
`❌ 处理组合订阅中的子订阅: ${
|
`❌ 处理组合订阅 ${collection.name} 中的子订阅: ${sub.name}时出现错误:${err}!`,
|
||||||
sub.name
|
|
||||||
}时出现错误:${err}!进度--${
|
|
||||||
100 * (processed / subnames.length).toFixed(1)
|
|
||||||
}%`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ async function getFlowInfo(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
const flowHeaders = await getFlowHeaders(
|
const flowHeaders = await getFlowHeaders(
|
||||||
url,
|
url,
|
||||||
undefined,
|
$arguments.flowUserAgent,
|
||||||
undefined,
|
undefined,
|
||||||
sub.proxy,
|
sub.proxy,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -563,10 +563,19 @@ async function syncArtifacts() {
|
|||||||
files.map((item) => [item.path, item]),
|
files.map((item) => [item.path, item]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url =
|
||||||
artifact.url = isGitLab
|
files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
? url
|
const new_url = isGitLab
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
? raw_url
|
||||||
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,10 +669,18 @@ async function syncArtifact(req, res) {
|
|||||||
isGitLab = true;
|
isGitLab = true;
|
||||||
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
files = Object.fromEntries(files.map((item) => [item.path, item]));
|
||||||
}
|
}
|
||||||
const url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
const raw_url = files[encodeURIComponent(artifact.name)]?.raw_url;
|
||||||
artifact.url = isGitLab
|
const new_url = isGitLab
|
||||||
? url
|
? raw_url
|
||||||
: url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
: raw_url?.replace(/\/raw\/[^/]*\/(.*)/, '/raw/$1');
|
||||||
|
$.info(
|
||||||
|
`上传配置完成\n文件列表: ${Object.keys(files).join(
|
||||||
|
', ',
|
||||||
|
)}\n当前文件: ${encodeURIComponent(
|
||||||
|
artifact.name,
|
||||||
|
)}\n响应返回的原始链接: ${raw_url}\n处理完的新链接: ${new_url}`,
|
||||||
|
);
|
||||||
|
artifact.url = new_url;
|
||||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||||
success(res, artifact);
|
success(res, artifact);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const customCacheKey = $arguments?.cacheKey
|
||||||
|
? `#sub-store-cached-custom-${$arguments?.cacheKey}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
// const downloadUrlMatch = url.match(/^\/api\/(file|module)\/(.+)/);
|
||||||
// if (downloadUrlMatch) {
|
// if (downloadUrlMatch) {
|
||||||
@@ -116,10 +119,24 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
}
|
}
|
||||||
if (shouldCache) {
|
if (shouldCache) {
|
||||||
resourceCache.set(id, body);
|
resourceCache.set(id, body);
|
||||||
|
if (customCacheKey) {
|
||||||
|
$.write(body, customCacheKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = body;
|
result = body;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (customCacheKey) {
|
||||||
|
const cached = $.read(customCacheKey);
|
||||||
|
if (cached) {
|
||||||
|
$.info(
|
||||||
|
`无法下载 URL ${url}: ${
|
||||||
|
e.message ?? e
|
||||||
|
}\n使用自定义缓存 ${$arguments?.cacheKey}`,
|
||||||
|
);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
|
throw new Error(`无法下载 URL ${url}: ${e.message ?? e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +146,12 @@ export default async function download(rawUrl, ua, timeout, proxy) {
|
|||||||
if ($arguments?.validCheck) {
|
if ($arguments?.validCheck) {
|
||||||
await validCheck(
|
await validCheck(
|
||||||
parseFlowHeaders(
|
parseFlowHeaders(
|
||||||
await getFlowHeaders(url, undefined, undefined, proxy),
|
await getFlowHeaders(
|
||||||
|
url,
|
||||||
|
$arguments.flowUserAgent,
|
||||||
|
undefined,
|
||||||
|
proxy,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
|||||||
const requestTimeout = timeout || defaultTimeout;
|
const requestTimeout = timeout || defaultTimeout;
|
||||||
const http = HTTP();
|
const http = HTTP();
|
||||||
try {
|
try {
|
||||||
// $.info(`使用 HEAD 方法获取流量信息: ${url}`);
|
$.info(
|
||||||
|
`使用 HEAD 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
const { headers } = await http.head({
|
const { headers } = await http.head({
|
||||||
url: url
|
url: url
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
@@ -76,11 +80,17 @@ export async function getFlowHeaders(rawUrl, ua, timeout, proxy) {
|
|||||||
flowInfo = getFlowField(headers);
|
flowInfo = getFlowField(headers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$.error(
|
$.error(
|
||||||
`使用 HEAD 方法获取流量信息失败: ${url}: ${e.message ?? e}`,
|
`使用 HEAD 方法获取流量信息失败: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}: ${e.message ?? e}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!flowInfo) {
|
if (!flowInfo) {
|
||||||
$.info(`使用 GET 方法获取流量信息: ${url}`);
|
$.info(
|
||||||
|
`使用 GET 方法获取流量信息: ${url}, User-Agent: ${
|
||||||
|
userAgent || ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
const { headers } = await http.get({
|
const { headers } = await http.get({
|
||||||
url: url
|
url: url
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
@@ -128,10 +138,10 @@ export function parseFlowHeaders(flowHeaders) {
|
|||||||
return { expires, total, usage: { upload, download } };
|
return { expires, total, usage: { upload, download } };
|
||||||
}
|
}
|
||||||
export function flowTransfer(flow, unit = 'B') {
|
export function flowTransfer(flow, unit = 'B') {
|
||||||
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
const unitList = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
let unitIndex = unitList.indexOf(unit);
|
let unitIndex = unitList.indexOf(unit);
|
||||||
|
|
||||||
return flow < 1024
|
return flow < 1024 || unitIndex === unitList.length - 1
|
||||||
? { value: flow.toFixed(1), unit: unit }
|
? { value: flow.toFixed(1), unit: unit }
|
||||||
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
: flowTransfer(flow / 1024, unitList[++unitIndex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,105 @@
|
|||||||
|
const ISOFlags = {
|
||||||
|
'🏳️🌈': ['EXP', 'BAND'],
|
||||||
|
'🇸🇱': ['TEST', 'SOS'],
|
||||||
|
'🇦🇩': ['AD', 'AND'],
|
||||||
|
'🇦🇪': ['AE', 'ARE'],
|
||||||
|
'🇦🇫': ['AF', 'AFG'],
|
||||||
|
'🇦🇱': ['AL', 'ALB'],
|
||||||
|
'🇦🇲': ['AM', 'ARM'],
|
||||||
|
'🇦🇷': ['AR', 'ARG'],
|
||||||
|
'🇦🇹': ['AT', 'AUT'],
|
||||||
|
'🇦🇺': ['AU', 'AUS'],
|
||||||
|
'🇦🇿': ['AZ', 'AZE'],
|
||||||
|
'🇧🇦': ['BA', 'BIH'],
|
||||||
|
'🇧🇩': ['BD', 'BGD'],
|
||||||
|
'🇧🇪': ['BE', 'BEL'],
|
||||||
|
'🇧🇬': ['BG', 'BGR'],
|
||||||
|
'🇧🇭': ['BH', 'BHR'],
|
||||||
|
'🇧🇷': ['BR', 'BRA'],
|
||||||
|
'🇧🇾': ['BY', 'BLR'],
|
||||||
|
'🇨🇦': ['CA', 'CAN'],
|
||||||
|
'🇨🇭': ['CH', 'CHE'],
|
||||||
|
'🇨🇱': ['CL', 'CHL'],
|
||||||
|
'🇨🇴': ['CO', 'COL'],
|
||||||
|
'🇨🇷': ['CR', 'CRI'],
|
||||||
|
'🇨🇾': ['CY', 'CYP'],
|
||||||
|
'🇨🇿': ['CZ', 'CZE'],
|
||||||
|
'🇩🇪': ['DE', 'DEU'],
|
||||||
|
'🇩🇰': ['DK', 'DNK'],
|
||||||
|
'🇪🇨': ['EC', 'ECU'],
|
||||||
|
'🇪🇪': ['EE', 'EST'],
|
||||||
|
'🇪🇬': ['EG', 'EGY'],
|
||||||
|
'🇪🇸': ['ES', 'ESP'],
|
||||||
|
'🇪🇺': ['EU'],
|
||||||
|
'🇫🇮': ['FI', 'FIN'],
|
||||||
|
'🇫🇷': ['FR', 'FRA'],
|
||||||
|
'🇬🇧': ['GB', 'GBR', 'UK'],
|
||||||
|
'🇬🇪': ['GE', 'GEO'],
|
||||||
|
'🇬🇷': ['GR', 'GRC'],
|
||||||
|
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
||||||
|
'🇭🇷': ['HR', 'HRV'],
|
||||||
|
'🇭🇺': ['HU', 'HUN'],
|
||||||
|
'🇯🇴': ['JO', 'JOR'],
|
||||||
|
'🇯🇵': ['JP', 'JPN', 'TYO'],
|
||||||
|
'🇰🇪': ['KE', 'KEN'],
|
||||||
|
'🇰🇬': ['KG', 'KGZ'],
|
||||||
|
'🇰🇭': ['KH', 'KGZ'],
|
||||||
|
'🇰🇵': ['KP', 'PRK'],
|
||||||
|
'🇰🇷': ['KR', 'KOR', 'SEL'],
|
||||||
|
'🇰🇿': ['KZ', 'KAZ'],
|
||||||
|
'🇮🇩': ['ID', 'IDN'],
|
||||||
|
'🇮🇪': ['IE', 'IRL'],
|
||||||
|
'🇮🇱': ['IL', 'ISR'],
|
||||||
|
'🇮🇲': ['IM', 'IMN'],
|
||||||
|
'🇮🇳': ['IN', 'IND'],
|
||||||
|
'🇮🇷': ['IR', 'IRN'],
|
||||||
|
'🇮🇸': ['IS', 'ISL'],
|
||||||
|
'🇮🇹': ['IT', 'ITA'],
|
||||||
|
'🇱🇹': ['LT', 'LTU'],
|
||||||
|
'🇱🇺': ['LU', 'LUX'],
|
||||||
|
'🇱🇻': ['LV', 'LVA'],
|
||||||
|
'🇲🇦': ['MA', 'MAR'],
|
||||||
|
'🇲🇩': ['MD', 'MDA'],
|
||||||
|
'🇳🇬': ['NG', 'NGA'],
|
||||||
|
'🇲🇰': ['MK', 'MKD'],
|
||||||
|
'🇲🇳': ['MN', 'MNG'],
|
||||||
|
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
||||||
|
'🇲🇹': ['MT', 'MLT'],
|
||||||
|
'🇲🇽': ['MX', 'MEX'],
|
||||||
|
'🇲🇾': ['MY', 'MYS'],
|
||||||
|
'🇳🇱': ['NL', 'NLD', 'AMS'],
|
||||||
|
'🇳🇴': ['NO', 'NOR'],
|
||||||
|
'🇳🇵': ['NP', 'NPL'],
|
||||||
|
'🇳🇿': ['NZ', 'NZL'],
|
||||||
|
'🇵🇦': ['PA', 'PAN'],
|
||||||
|
'🇵🇪': ['PE', 'PER'],
|
||||||
|
'🇵🇭': ['PH', 'PHL'],
|
||||||
|
'🇵🇰': ['PK', 'PAK'],
|
||||||
|
'🇵🇱': ['PL', 'POL'],
|
||||||
|
'🇵🇷': ['PR', 'PRI'],
|
||||||
|
'🇵🇹': ['PT', 'PRT'],
|
||||||
|
'🇵🇾': ['PY', 'PRY'],
|
||||||
|
'🇷🇴': ['RO', 'ROU'],
|
||||||
|
'🇷🇸': ['RS', 'SRB'],
|
||||||
|
'🇷🇪': ['RE', 'REU'],
|
||||||
|
'🇷🇺': ['RU', 'RUS'],
|
||||||
|
'🇸🇦': ['SA', 'SAU'],
|
||||||
|
'🇸🇪': ['SE', 'SWE'],
|
||||||
|
'🇸🇬': ['SG', 'SGP'],
|
||||||
|
'🇸🇮': ['SI', 'SVN'],
|
||||||
|
'🇸🇰': ['SK', 'SVK'],
|
||||||
|
'🇹🇭': ['TH', 'THA'],
|
||||||
|
'🇹🇳': ['TN', 'TUN'],
|
||||||
|
'🇹🇷': ['TR', 'TUR'],
|
||||||
|
'🇹🇼': ['TW', 'TWN', 'CHT', 'HINET', 'ROC'],
|
||||||
|
'🇺🇦': ['UA', 'UKR'],
|
||||||
|
'🇺🇸': ['US', 'USA', 'LAX', 'SFO', 'SJC'],
|
||||||
|
'🇺🇾': ['UY', 'URY'],
|
||||||
|
'🇻🇪': ['VE', 'VEN'],
|
||||||
|
'🇻🇳': ['VN', 'VNM'],
|
||||||
|
'🇿🇦': ['ZA', 'ZAF', 'JNB'],
|
||||||
|
'🇨🇳': ['CN', 'CHN', 'BACK'],
|
||||||
|
};
|
||||||
// get proxy flag according to its name
|
// get proxy flag according to its name
|
||||||
export function getFlag(name) {
|
export function getFlag(name) {
|
||||||
// flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js
|
// flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js
|
||||||
@@ -65,6 +167,7 @@ export function getFlag(name) {
|
|||||||
'广德',
|
'广德',
|
||||||
'法兰克福',
|
'法兰克福',
|
||||||
'Frankfurt',
|
'Frankfurt',
|
||||||
|
'德意志',
|
||||||
],
|
],
|
||||||
'🇩🇰': ['Denmark', '丹麦', '丹麥'],
|
'🇩🇰': ['Denmark', '丹麦', '丹麥'],
|
||||||
'🇪🇨': ['Ecuador', '厄瓜多尔'],
|
'🇪🇨': ['Ecuador', '厄瓜多尔'],
|
||||||
@@ -283,108 +386,6 @@ export function getFlag(name) {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ISOFlags = {
|
|
||||||
'🏳️🌈': ['EXP', 'BAND'],
|
|
||||||
'🇸🇱': ['TEST', 'SOS'],
|
|
||||||
'🇦🇩': ['AD', 'AND'],
|
|
||||||
'🇦🇪': ['AE', 'ARE'],
|
|
||||||
'🇦🇫': ['AF', 'AFG'],
|
|
||||||
'🇦🇱': ['AL', 'ALB'],
|
|
||||||
'🇦🇲': ['AM', 'ARM'],
|
|
||||||
'🇦🇷': ['AR', 'ARG'],
|
|
||||||
'🇦🇹': ['AT', 'AUT'],
|
|
||||||
'🇦🇺': ['AU', 'AUS'],
|
|
||||||
'🇦🇿': ['AZ', 'AZE'],
|
|
||||||
'🇧🇦': ['BA', 'BIH'],
|
|
||||||
'🇧🇩': ['BD', 'BGD'],
|
|
||||||
'🇧🇪': ['BE', 'BEL'],
|
|
||||||
'🇧🇬': ['BG', 'BGR'],
|
|
||||||
'🇧🇭': ['BH', 'BHR'],
|
|
||||||
'🇧🇷': ['BR', 'BRA'],
|
|
||||||
'🇧🇾': ['BY', 'BLR'],
|
|
||||||
'🇨🇦': ['CA', 'CAN'],
|
|
||||||
'🇨🇭': ['CH', 'CHE'],
|
|
||||||
'🇨🇱': ['CL', 'CHL'],
|
|
||||||
'🇨🇴': ['CO', 'COL'],
|
|
||||||
'🇨🇷': ['CR', 'CRI'],
|
|
||||||
'🇨🇾': ['CY', 'CYP'],
|
|
||||||
'🇨🇿': ['CZ', 'CZE'],
|
|
||||||
'🇩🇪': ['DE', 'DEU'],
|
|
||||||
'🇩🇰': ['DK', 'DNK'],
|
|
||||||
'🇪🇨': ['EC', 'ECU'],
|
|
||||||
'🇪🇪': ['EE', 'EST'],
|
|
||||||
'🇪🇬': ['EG', 'EGY'],
|
|
||||||
'🇪🇸': ['ES', 'ESP'],
|
|
||||||
'🇪🇺': ['EU'],
|
|
||||||
'🇫🇮': ['FI', 'FIN'],
|
|
||||||
'🇫🇷': ['FR', 'FRA'],
|
|
||||||
'🇬🇧': ['GB', 'GBR', 'UK'],
|
|
||||||
'🇬🇪': ['GE', 'GEO'],
|
|
||||||
'🇬🇷': ['GR', 'GRC'],
|
|
||||||
'🇭🇰': ['HK', 'HKG', 'HKT', 'HKBN', 'HGC', 'WTT', 'CMI'],
|
|
||||||
'🇭🇷': ['HR', 'HRV'],
|
|
||||||
'🇭🇺': ['HU', 'HUN'],
|
|
||||||
'🇯🇴': ['JO', 'JOR'],
|
|
||||||
'🇯🇵': ['JP', 'JPN'],
|
|
||||||
'🇰🇪': ['KE', 'KEN'],
|
|
||||||
'🇰🇬': ['KG', 'KGZ'],
|
|
||||||
'🇰🇭': ['KH', 'KGZ'],
|
|
||||||
'🇰🇵': ['KP', 'PRK'],
|
|
||||||
'🇰🇷': ['KR', 'KOR'],
|
|
||||||
'🇰🇿': ['KZ', 'KAZ'],
|
|
||||||
'🇮🇩': ['ID', 'IDN'],
|
|
||||||
'🇮🇪': ['IE', 'IRL'],
|
|
||||||
'🇮🇱': ['IL', 'ISR'],
|
|
||||||
'🇮🇲': ['IM', 'IMN'],
|
|
||||||
'🇮🇳': ['IN', 'IND'],
|
|
||||||
'🇮🇷': ['IR', 'IRN'],
|
|
||||||
'🇮🇸': ['IS', 'ISL'],
|
|
||||||
'🇮🇹': ['IT', 'ITA'],
|
|
||||||
'🇱🇹': ['LT', 'LTU'],
|
|
||||||
'🇱🇺': ['LU', 'LUX'],
|
|
||||||
'🇱🇻': ['LV', 'LVA'],
|
|
||||||
'🇲🇦': ['MA', 'MAR'],
|
|
||||||
'🇲🇩': ['MD', 'MDA'],
|
|
||||||
'🇳🇬': ['NG', 'NGA'],
|
|
||||||
'🇲🇰': ['MK', 'MKD'],
|
|
||||||
'🇲🇳': ['MN', 'MNG'],
|
|
||||||
'🇲🇴': ['MO', 'MAC', 'CTM'],
|
|
||||||
'🇲🇹': ['MT', 'MLT'],
|
|
||||||
'🇲🇽': ['MX', 'MEX'],
|
|
||||||
'🇲🇾': ['MY', 'MYS'],
|
|
||||||
'🇳🇱': ['NL', 'NLD'],
|
|
||||||
'🇳🇴': ['NO', 'NOR'],
|
|
||||||
'🇳🇵': ['NP', 'NPL'],
|
|
||||||
'🇳🇿': ['NZ', 'NZL'],
|
|
||||||
'🇵🇦': ['PA', 'PAN'],
|
|
||||||
'🇵🇪': ['PE', 'PER'],
|
|
||||||
'🇵🇭': ['PH', 'PHL'],
|
|
||||||
'🇵🇰': ['PK', 'PAK'],
|
|
||||||
'🇵🇱': ['PL', 'POL'],
|
|
||||||
'🇵🇷': ['PR', 'PRI'],
|
|
||||||
'🇵🇹': ['PT', 'PRT'],
|
|
||||||
'🇵🇾': ['PY', 'PRY'],
|
|
||||||
'🇷🇴': ['RO', 'ROU'],
|
|
||||||
'🇷🇸': ['RS', 'SRB'],
|
|
||||||
'🇷🇪': ['RE', 'REU'],
|
|
||||||
'🇷🇺': ['RU', 'RUS'],
|
|
||||||
'🇸🇦': ['SA', 'SAU'],
|
|
||||||
'🇸🇪': ['SE', 'SWE'],
|
|
||||||
'🇸🇬': ['SG', 'SGP'],
|
|
||||||
'🇸🇮': ['SI', 'SVN'],
|
|
||||||
'🇸🇰': ['SK', 'SVK'],
|
|
||||||
'🇹🇭': ['TH', 'THA'],
|
|
||||||
'🇹🇳': ['TN', 'TUN'],
|
|
||||||
'🇹🇷': ['TR', 'TUR'],
|
|
||||||
'🇹🇼': ['TW', 'TWN', 'CHT', 'HINET', 'ROC'],
|
|
||||||
'🇺🇦': ['UA', 'UKR'],
|
|
||||||
'🇺🇸': ['US', 'USA', 'LAX', 'SFO'],
|
|
||||||
'🇺🇾': ['UY', 'URY'],
|
|
||||||
'🇻🇪': ['VE', 'VEN'],
|
|
||||||
'🇻🇳': ['VN', 'VNM'],
|
|
||||||
'🇿🇦': ['ZA', 'ZAF'],
|
|
||||||
'🇨🇳': ['CN', 'CHN', 'BACK'],
|
|
||||||
};
|
|
||||||
// 原旗帜或空
|
// 原旗帜或空
|
||||||
let Flag =
|
let Flag =
|
||||||
name.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)?.[0] ||
|
name.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/)?.[0] ||
|
||||||
@@ -398,7 +399,9 @@ export function getFlag(name) {
|
|||||||
// 不精确匹配(只要包含就算,忽略大小写)
|
// 不精确匹配(只要包含就算,忽略大小写)
|
||||||
keywords.some((keyword) => RegExp(`${keyword}`, 'i').test(name))
|
keywords.some((keyword) => RegExp(`${keyword}`, 'i').test(name))
|
||||||
) {
|
) {
|
||||||
//console.log(`newFlag = ${flag}`)
|
if (/内蒙古/.test(name) && ['🇲🇳'].includes(flag)) {
|
||||||
|
return (Flag = '🇨🇳');
|
||||||
|
}
|
||||||
return (Flag = flag);
|
return (Flag = flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,6 +419,11 @@ export function getFlag(name) {
|
|||||||
return (Flag = flag);
|
return (Flag = flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(`Final Flag = ${Flag}`)
|
//console.log(`Final Flag = ${Flag}`)
|
||||||
return Flag;
|
return Flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getISO(name) {
|
||||||
|
return ISOFlags[getFlag(name)]?.[0];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function getPlatformFromHeaders(headers) {
|
export function getUserAgentFromHeaders(headers) {
|
||||||
const keys = Object.keys(headers);
|
const keys = Object.keys(headers);
|
||||||
let UA = '';
|
let UA = '';
|
||||||
let ua = '';
|
let ua = '';
|
||||||
@@ -9,6 +9,9 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { UA, ua };
|
||||||
|
}
|
||||||
|
export function getPlatformFromUserAgent({ ua, UA }) {
|
||||||
if (UA.indexOf('Quantumult%20X') !== -1) {
|
if (UA.indexOf('Quantumult%20X') !== -1) {
|
||||||
return 'QX';
|
return 'QX';
|
||||||
} else if (UA.indexOf('Surfboard') !== -1) {
|
} else if (UA.indexOf('Surfboard') !== -1) {
|
||||||
@@ -38,3 +41,7 @@ export function getPlatformFromHeaders(headers) {
|
|||||||
return 'JSON';
|
return 'JSON';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function getPlatformFromHeaders(headers) {
|
||||||
|
const { UA, ua } = getUserAgentFromHeaders(headers);
|
||||||
|
return getPlatformFromUserAgent({ ua, UA });
|
||||||
|
}
|
||||||
56
backend/src/vendor/open-api.js
vendored
56
backend/src/vendor/open-api.js
vendored
@@ -323,36 +323,46 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
|||||||
const request = isNode
|
const request = isNode
|
||||||
? eval("require('request')")
|
? eval("require('request')")
|
||||||
: $httpClient;
|
: $httpClient;
|
||||||
request[method.toLowerCase()](
|
const opts = JSON.parse(JSON.stringify(options));
|
||||||
JSON.parse(JSON.stringify(options)),
|
if (!isNode && opts.timeout) {
|
||||||
(err, response, body) => {
|
opts.timeout++;
|
||||||
// if (err) {
|
let unit = 'ms';
|
||||||
// console.log(err);
|
// 这些客户端单位为 s
|
||||||
// } else {
|
if (isSurge || isStash || isShadowRocket) {
|
||||||
// console.log({
|
opts.timeout = Math.ceil(opts.timeout / 1000);
|
||||||
// statusCode:
|
unit = 's';
|
||||||
// response.status || response.statusCode,
|
}
|
||||||
// headers: response.headers,
|
// Loon 为 ms
|
||||||
// body,
|
// console.log(`[httpClient timeout] ${opts.timeout}${unit}`);
|
||||||
// });
|
}
|
||||||
// }
|
request[method.toLowerCase()](opts, (err, response, body) => {
|
||||||
|
// if (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// } else {
|
||||||
|
// console.log({
|
||||||
|
// statusCode:
|
||||||
|
// response.status || response.statusCode,
|
||||||
|
// headers: response.headers,
|
||||||
|
// body,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else
|
else
|
||||||
resolve({
|
resolve({
|
||||||
statusCode:
|
statusCode: response.status || response.statusCode,
|
||||||
response.status || response.statusCode,
|
headers: response.headers,
|
||||||
headers: response.headers,
|
body,
|
||||||
body,
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeoutid;
|
let timeoutid;
|
||||||
|
|
||||||
const timer = timeout
|
const timer = timeout
|
||||||
? new Promise((_, reject) => {
|
? new Promise((_, reject) => {
|
||||||
|
// console.log(`[request timeout] ${timeout}ms`);
|
||||||
timeoutid = setTimeout(() => {
|
timeoutid = setTimeout(() => {
|
||||||
events.onTimeout();
|
events.onTimeout();
|
||||||
return reject(
|
return reject(
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ hostname=sub.store
|
|||||||
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\/((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://github.com/sub-store-org/Sub-Store/releases/latest/download/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://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js, 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
|
||||||
@@ -13,15 +13,13 @@ Telegram 频道: [`https://t.me/cool_scripts` ](https://t.me/cool_scripts)
|
|||||||
|
|
||||||
### 2. Surge
|
### 2. Surge
|
||||||
|
|
||||||
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的分类和参数设置): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
0. 最新 Surge iOS TestFlight 版本 可使用 Beta 版(支持最新 Surge iOS TestFlight 版本的特性): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge-Beta.sgmodule)
|
||||||
|
|
||||||
1. 官方默认版模块(目前不带 ability 参数, 不保证以后不会改动): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
1. 官方默认版模块(支持 App 内使用编辑参数): [`https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule`](https://raw.githubusercontent.com/sub-store-org/Sub-Store/master/config/Surge.sgmodule)
|
||||||
|
|
||||||
2. 固定带 ability 参数版本,可能会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者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)
|
||||||
|
|
||||||
> 最新 Surge iOS TestFlight 版本应该没有内存问题了 可以大胆尝试带 ability 参数版本
|
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
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#!name=Sub-Store(β)
|
#!name=Sub-Store(β)
|
||||||
#!desc=支持最新 Surge iOS TestFlight 版本的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync"
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto
|
||||||
#!arguments-desc="\n1️⃣ ability\n默认已开启测落地能力\n需要配合脚本操作\n如 https://raw.githubusercontent.com/Keywos/rule/main/cname.js\n⚠️ Surge 上时候可能会爆内存\n不需要使用的时候应该关闭\n填写任意其他值关闭\n\n2️⃣ cronexp\n同步配置定时任务\n默认为每天 23 点 55 分\n\n3️⃣ sync\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务"
|
#!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\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
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://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-1.min.js,requires-body=true,timeout=120,ability="{{{ability}}}"
|
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://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
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=120,script-path=https://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js
|
{{{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}}}
|
||||||
@@ -8,6 +8,6 @@ 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://github.com/sub-store-org/Sub-Store/releases/latest/download/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://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
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://github.com/sub-store-org/Sub-Store/releases/latest/download/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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 可能会爆内存, 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 23 点 55 分
|
#!desc=高级订阅管理工具 @Peng-YM 带 ability 参数版本, 使用 jsc 引擎时, 可能会爆内存, 如果不需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用不带 ability 参数版本. 定时任务默认为每天 23 点 55 分
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
@@ -7,6 +7,6 @@ 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://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 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://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store-0.min.js,requires-body=true
|
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://github.com/sub-store-org/Sub-Store/releases/latest/download/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
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#!name=Sub-Store
|
#!name=Sub-Store
|
||||||
#!desc=高级订阅管理工具 @Peng-YM 无 ability 参数版本,不会爆内存, 如果需要使用指定节点功能 例如[加旗帜脚本或者cname脚本] 可以用带 ability 参数. 定时任务默认为每天 23 点 55 分
|
#!desc=支持 Surge 正式版的参数设置功能. 测落地功能 ability: http-client-policy, 同步配置的定时 cronexp: 55 23 * * *
|
||||||
#!category=订阅管理
|
#!category=订阅管理
|
||||||
|
#!arguments=ability:http-client-policy,cronexp:55 23 * * *,sync:"Sub-Store Sync",timeout:120,engine:auto
|
||||||
|
#!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\n3️⃣ sync\n\n自定义定时任务名\n便于在脚本编辑器中选择\n若设为 # 可取消定时任务\n\n4️⃣ timeout\n\n超时, 单位为秒\n\n5️⃣ engine\n\n默认为自动使用 webview 引擎, 可设为指定 jsc, 但 jsc 容易爆内存
|
||||||
|
|
||||||
[MITM]
|
[MITM]
|
||||||
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://github.com/sub-store-org/Sub-Store/releases/latest/download/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={{{timeout}}},ability="{{{ability}}}",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
|
|
||||||
|
|
||||||
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
|
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://github.com/sub-store-org/Sub-Store/releases/latest/download/cron-sync-artifacts.min.js,engine={{{engine}}}
|
||||||
@@ -9,7 +9,8 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
// 可在预览界面点击节点查看 JSON 结构 或查看 `target=JSON` 的通用订阅
|
||||||
// 1. `no-resolve` 为不解析域名
|
// 1. `no-resolve` 为不解析域名
|
||||||
// 2. 域名解析后 会多一个 `resolved` 字段
|
// 2. 域名解析后 会多一个 `resolved` 字段
|
||||||
// 3. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
// 3. 域名解析后会有`_IPv4`, `_IPv6`, `_IP`(若有多个步骤, 只取第一次成功的 v4 或 v6 数据), `_domain` 字段
|
||||||
|
// 4. 节点字段 `exec` 为 `ssr-local` 路径, 默认 `/usr/local/bin/ssr-local`; 端口从 10000 开始递增(暂不支持配置)
|
||||||
|
|
||||||
// $arguments 为传入的脚本参数
|
// $arguments 为传入的脚本参数
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// scriptResourceCache 缓存
|
// scriptResourceCache 缓存
|
||||||
// 可参考 https://t.me/zhetengsha/1003
|
// 可参考 https://t.me/zhetengsha/1003
|
||||||
|
// const cache = scriptResourceCache
|
||||||
|
// cache.set(id, data)
|
||||||
|
// cache.get(id)
|
||||||
|
|
||||||
// ProxyUtils 为节点处理工具
|
// ProxyUtils 为节点处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/1066
|
// 可参考 https://t.me/zhetengsha/1066
|
||||||
@@ -33,8 +37,20 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
// isIPv6,
|
// isIPv6,
|
||||||
// isIP,
|
// isIP,
|
||||||
// yaml, // yaml 解析和生成
|
// yaml, // yaml 解析和生成
|
||||||
|
// getFlag, // 获取 emoji 旗帜
|
||||||
|
// getISO, // 获取 ISO 3166-1 alpha-2 代码
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 示例: 给节点名添加前缀
|
||||||
|
// $server.name = `[${ProxyUtils.getISO($server.name)}] ${$server.name}`
|
||||||
|
|
||||||
|
// 示例: 从 sni 文件中读取内容并进行节点操作
|
||||||
|
// const sni = await produceArtifact({
|
||||||
|
// type: 'file',
|
||||||
|
// name: 'sni' // 文件名
|
||||||
|
// });
|
||||||
|
// $server.sni = sni
|
||||||
|
|
||||||
// 1. Surge 输出 WireGuard 完整配置
|
// 1. Surge 输出 WireGuard 完整配置
|
||||||
|
|
||||||
// let proxies = await produceArtifact({
|
// let proxies = await produceArtifact({
|
||||||
@@ -49,7 +65,10 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// 2. sing-box
|
// 2. sing-box
|
||||||
|
|
||||||
// 但是一般不需要这样用, 可参考 1. https://t.me/zhetengsha/1111 和 2. https://t.me/zhetengsha/1070
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1241
|
||||||
|
|
||||||
// let singboxProxies = await produceArtifact({
|
// let singboxProxies = await produceArtifact({
|
||||||
// type: 'subscription', // type: 'subscription' 或 'collection'
|
// type: 'subscription', // type: 'subscription' 或 'collection'
|
||||||
@@ -63,24 +82,42 @@ function operator(proxies = [], targetPlatform, context) {
|
|||||||
|
|
||||||
// 3. clash.meta
|
// 3. clash.meta
|
||||||
|
|
||||||
// 但是一般不需要这样用, 可参考 1. https://t.me/zhetengsha/1111 和 2. https://t.me/zhetengsha/1070
|
// 但是一般不需要这样用, 可参考
|
||||||
|
// 1. https://t.me/zhetengsha/1111
|
||||||
|
// 2. https://t.me/zhetengsha/1070
|
||||||
|
// 3. https://t.me/zhetengsha/1234
|
||||||
|
|
||||||
// let clashMetaProxies = await produceArtifact({
|
// let clashMetaProxies = await produceArtifact({
|
||||||
// type: 'subscription',
|
// type: 'subscription',
|
||||||
// name: 'sub',
|
// name: 'sub',
|
||||||
// platform: 'ClashMeta',
|
// platform: 'ClashMeta',
|
||||||
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( ProxyUtils.yaml.safeLoad('YAML String').proxies )
|
// produceType: 'internal' // 'internal' produces an Array, otherwise produces a String( ProxyUtils.yaml.safeLoad('YAML String').proxies )
|
||||||
// }))
|
// })
|
||||||
|
|
||||||
// // YAML
|
// // YAML
|
||||||
|
// ProxyUtils.yaml.load('YAML String')
|
||||||
|
// ProxyUtils.yaml.safeLoad('YAML String')
|
||||||
// $content = ProxyUtils.yaml.safeDump({})
|
// $content = ProxyUtils.yaml.safeDump({})
|
||||||
|
// $content = ProxyUtils.yaml.dump({})
|
||||||
|
|
||||||
|
// 一个往文件里插入本地节点的例子:
|
||||||
|
// const yaml = ProxyUtils.yaml.safeLoad($content ?? $files[0])
|
||||||
|
// let clashMetaProxies = await produceArtifact({
|
||||||
|
// type: 'collection',
|
||||||
|
// name: '机场',
|
||||||
|
// platform: 'ClashMeta',
|
||||||
|
// produceType: 'internal'
|
||||||
|
// })
|
||||||
|
// yaml.proxies.unshift(...clashMetaProxies)
|
||||||
|
// $content = ProxyUtils.yaml.dump(yaml)
|
||||||
|
|
||||||
|
|
||||||
// { $content, $files } will be passed to the next operator
|
// { $content, $files } will be passed to the next operator
|
||||||
// $content is the final content of the file
|
// $content is the final content of the file
|
||||||
|
|
||||||
// flowUtils 为机场订阅流量信息处理工具
|
// flowUtils 为机场订阅流量信息处理工具
|
||||||
// 可参考 https://t.me/zhetengsha/948
|
// 可参考:
|
||||||
// https://github.com/sub-store-org/Sub-Store/blob/31b6dd0507a9286d6ab834ec94ad3050f6bdc86b/backend/src/utils/download.js#L104
|
// 1. https://t.me/zhetengsha/948
|
||||||
|
|
||||||
// context 为传入的上下文
|
// context 为传入的上下文
|
||||||
// 有三种情况, 按需判断
|
// 有三种情况, 按需判断
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* 【字体】
|
* 【字体】
|
||||||
* 可参考:https://www.dute.org/weird-fonts
|
* 可参考:https://www.dute.org/weird-fonts
|
||||||
* serif-bold, serif-italic, serif-bold-italic, sans-serif-regular, sans-serif-bold-italic, script-regular, script-bold, fraktur-regular, fraktur-bold, monospace-regular, double-struck-bold, circle-regular, square-regular
|
* serif-bold, serif-italic, serif-bold-italic, sans-serif-regular, sans-serif-bold-italic, script-regular, script-bold, fraktur-regular, fraktur-bold, monospace-regular, double-struck-bold, circle-regular, square-regular, modifier-letter(小写没有 q, 用 ᵠ 替代. 大写缺的太多, 用小写替代)
|
||||||
*
|
*
|
||||||
* 【示例】
|
* 【示例】
|
||||||
* 1️⃣ 设置所有格式为 "serif-bold"
|
* 1️⃣ 设置所有格式为 "serif-bold"
|
||||||
@@ -31,6 +31,7 @@ function operator(proxies) {
|
|||||||
"double-struck-bold": ["𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝔸","𝔹","ℂ","𝔻","𝔼","𝔽","𝔾","ℍ","𝕀","𝕁","𝕂","𝕃","𝕄","ℕ","𝕆","ℙ","ℚ","ℝ","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐","ℤ"],
|
"double-struck-bold": ["𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝔸","𝔹","ℂ","𝔻","𝔼","𝔽","𝔾","ℍ","𝕀","𝕁","𝕂","𝕃","𝕄","ℕ","𝕆","ℙ","ℚ","ℝ","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐","ℤ"],
|
||||||
"circle-regular": ["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ"],
|
"circle-regular": ["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ"],
|
||||||
"square-regular": ["0","1","2","3","4","5","6","7","8","9","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"],
|
"square-regular": ["0","1","2","3","4","5","6","7","8","9","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"],
|
||||||
|
"modifier-letter": ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", "ᵃ", "ᵇ", "ᶜ", "ᵈ", "ᵉ", "ᶠ", "ᵍ", "ʰ", "ⁱ", "ʲ", "ᵏ", "ˡ", "ᵐ", "ⁿ", "ᵒ", "ᵖ", "ᵠ", "ʳ", "ˢ", "ᵗ", "ᵘ", "ᵛ", "ʷ", "ˣ", "ʸ", "ᶻ", "ᴬ", "ᴮ", "ᶜ", "ᴰ", "ᴱ", "ᶠ", "ᴳ", "ʰ", "ᴵ", "ᴶ", "ᴷ", "ᴸ", "ᴹ", "ᴺ", "ᴼ", "ᴾ", "ᵠ", "ᴿ", "ˢ", "ᵀ", "ᵁ", "ᵛ", "ᵂ", "ˣ", "ʸ", "ᶻ"],
|
||||||
};
|
};
|
||||||
|
|
||||||
// charCode => index in `TABLE`
|
// charCode => index in `TABLE`
|
||||||
|
|||||||
Reference in New Issue
Block a user