Compare commits

...

4 Commits

Author SHA1 Message Date
xream
c617eabd90 feat: 域名解析新增 IP4P 2024-03-05 00:56:24 +08:00
xream
739100c873 feat: Stash/clash.meta(mihomo) 支持 interface-name 字段 2024-03-04 11:43:07 +08:00
xream
a4384f4f13 fix: 修复 Clash 节点名为 binary 的情况 2024-03-03 14:33:49 +08:00
xream
468d136f0e ci: git push assets to "release" branch 2024-02-28 23:07:16 +08:00
6 changed files with 190 additions and 29 deletions

View File

@@ -59,6 +59,17 @@ jobs:
./backend/dist/sub-store-parser.loon.min.js
./backend/dist/cron-sync-artifacts.min.js
./backend/dist/sub-store.bundle.js
- name: Git push assets to "release" branch
run: |
cd backend/dist || exit 1
git init
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b release
git add .
git commit -m "release: ${{ steps.tag.outputs.release_tag }}"
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
git push -f -u origin release
- name: Sync to GitLab
env:
GITLAB_PIPELINE_TOKEN: ${{ secrets.GITLAB_PIPELINE_TOKEN }}

View File

@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.230",
"version": "2.14.237",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@@ -1,6 +1,12 @@
import YAML from '@/utils/yaml';
import download from '@/utils/download';
import { isIPv4, isIPv6, isValidPortNumber, isNotBlank } from '@/utils';
import {
isIPv4,
isIPv6,
isValidPortNumber,
isNotBlank,
utf8ArrayToStr,
} from '@/utils';
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
import PROXY_PREPROCESSORS from './preprocessors';
import PROXY_PRODUCERS from './producers';
@@ -257,6 +263,10 @@ function safeMatch(parser, line) {
}
function lastParse(proxy) {
if (proxy.interface) {
proxy['interface-name'] = proxy.interface;
delete proxy.interface;
}
if (isValidPortNumber(proxy.port)) {
proxy.port = parseInt(proxy.port, 10);
}
@@ -370,6 +380,18 @@ function lastParse(proxy) {
}
}
}
if (typeof proxy.name !== 'string') {
try {
if (proxy.name?.data) {
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
} else {
proxy.name = utf8ArrayToStr(proxy.name);
}
} catch (e) {
$.error(`proxy.name decode failed\nReason: ${e}`);
proxy.name = `${proxy.type} ${proxy.server}:${proxy.port}`;
}
}
return proxy;
}

View File

@@ -357,11 +357,41 @@ function ScriptOperator(script, targetPlatform, $arguments, source) {
};
}
function parseIP4P(IP4P) {
let server;
let port;
try {
if (!/^2001::[^:]+:[^:]+:[^:]+$/.test(IP4P)) {
throw new Error(`Invalid IP4P: ${IP4P}`);
}
let array = IP4P.split(':');
port = parseInt(array[2], 16);
let ipab = parseInt(array[3], 16);
let ipcd = parseInt(array[4], 16);
let ipa = ipab >> 8;
let ipb = ipab & 0xff;
let ipc = ipcd >> 8;
let ipd = ipcd & 0xff;
server = `${ipa}.${ipb}.${ipc}.${ipd}`;
if (port <= 0 || port > 65535) {
throw new Error(`Invalid port number: ${port}`);
}
if (!isIPv4(server)) {
throw new Error(`Invalid IP address: ${server}`);
}
} catch (e) {
// throw new Error(`IP4P 解析失败: ${e}`);
$.error(`IP4P 解析失败: ${e}`);
}
return { server, port };
}
const DOMAIN_RESOLVERS = {
Google: async function (domain, type) {
Google: async function (domain, type, noCache) {
const id = hex_md5(`GOOGLE:${domain}:${type}`);
const cached = resourceCache.get(id);
if (cached) return cached;
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `https://8.8.4.4/resolve?name=${encodeURIComponent(
domain,
@@ -382,10 +412,13 @@ const DOMAIN_RESOLVERS = {
resourceCache.set(id, result);
return result;
},
'IP-API': async function (domain) {
'IP-API': async function (domain, type, noCache) {
if (['IPv6'].includes(type)) {
throw new Error(`域名解析服务提供方 IP-API 不支持 ${type}`);
}
const id = hex_md5(`IP-API:${domain}`);
const cached = resourceCache.get(id);
if (cached) return cached;
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `http://ip-api.com/json/${encodeURIComponent(
domain,
@@ -399,10 +432,10 @@ const DOMAIN_RESOLVERS = {
resourceCache.set(id, result);
return result;
},
Cloudflare: async function (domain, type) {
Cloudflare: async function (domain, type, noCache) {
const id = hex_md5(`CLOUDFLARE:${domain}:${type}`);
const cached = resourceCache.get(id);
if (cached) return cached;
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `https://1.0.0.1/dns-query?name=${encodeURIComponent(
domain,
@@ -423,10 +456,10 @@ const DOMAIN_RESOLVERS = {
resourceCache.set(id, result);
return result;
},
Ali: async function (domain, type) {
Ali: async function (domain, type, noCache) {
const id = hex_md5(`ALI:${domain}:${type}`);
const cached = resourceCache.get(id);
if (cached) return cached;
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `http://223.6.6.6/resolve?name=${encodeURIComponent(
domain,
@@ -443,10 +476,10 @@ const DOMAIN_RESOLVERS = {
resourceCache.set(id, result);
return result;
},
Tencent: async function (domain, type) {
Tencent: async function (domain, type, noCache) {
const id = hex_md5(`ALI:${domain}:${type}`);
const cached = resourceCache.get(id);
if (cached) return cached;
if (!noCache && cached) return cached;
const resp = await $.http.get({
url: `http://119.28.28.28/d?type=${
type === 'IPv6' ? 'AAAA' : 'A'
@@ -465,10 +498,13 @@ const DOMAIN_RESOLVERS = {
},
};
function ResolveDomainOperator({ provider, type, filter }) {
if (type === 'IPv6' && ['IP-API'].includes(provider)) {
throw new Error(`域名解析服务提供方 ${provider} 不支持 IPv6`);
function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
console.log(`cache`, cache);
if (['IPv6', 'IP4P'].includes(_type) && ['IP-API'].includes(provider)) {
throw new Error(`域名解析服务提供方 ${provider} 不支持 ${_type}`);
}
let type = ['IPv6', 'IP4P'].includes(_type) ? 'IPv6' : 'IPv4';
const resolver = DOMAIN_RESOLVERS[provider];
if (!resolver) {
throw new Error(`找不到域名解析服务提供方: ${provider}`);
@@ -490,7 +526,7 @@ function ResolveDomainOperator({ provider, type, filter }) {
const currentBatch = [];
for (let domain of totalDomain.splice(0, limit)) {
currentBatch.push(
resolver(domain, type)
resolver(domain, type, cache === 'disabled')
.then((ip) => {
results[domain] = ip;
$.info(
@@ -509,8 +545,19 @@ function ResolveDomainOperator({ provider, type, filter }) {
proxies.forEach((p) => {
if (!p['no-resolve']) {
if (results[p.server]) {
p.server = results[p.server];
p.resolved = true;
if (_type === 'IP4P') {
const { server, port } = parseIP4P(
results[p.server],
);
if (server && port) {
p.server = server;
p.port = port;
p.resolved = true;
}
} else {
p.server = results[p.server];
p.resolved = true;
}
} else {
p.resolved = false;
}

View File

@@ -132,7 +132,10 @@ function shadowsocks(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -229,7 +232,10 @@ function trojan(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -315,7 +321,10 @@ function vmess(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -385,7 +394,10 @@ function ssh(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// block-quic
result.appendIfPresent(`,block-quic=${proxy['block-quic']}`, 'block-quic');
@@ -445,7 +457,10 @@ function http(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -522,7 +537,10 @@ function socks5(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -597,7 +615,10 @@ function snell(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -687,7 +708,10 @@ function tuic(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -761,7 +785,10 @@ ${proxy.name}=wireguard`);
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -860,7 +887,10 @@ function wireguard_surge(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {
@@ -936,7 +966,10 @@ function hysteria2(proxy) {
`,allow-other-interface=${proxy['allow-other-interface']}`,
'allow-other-interface',
);
result.appendIfPresent(`,interface=${proxy['interface']}`, 'interface');
result.appendIfPresent(
`,interface=${proxy['interface-name']}`,
'interface-name',
);
// shadow-tls
if (isPresent(proxy, 'shadow-tls-password')) {

View File

@@ -35,6 +35,53 @@ function getIfPresent(obj, defaultValue) {
return isPresent(obj) ? obj : defaultValue;
}
const utf8ArrayToStr =
typeof TextDecoder !== 'undefined'
? (v) => new TextDecoder().decode(new Uint8Array(v))
: (function () {
var charCache = new Array(128); // Preallocate the cache for the common single byte chars
var charFromCodePt = String.fromCodePoint || String.fromCharCode;
var result = [];
return function (array) {
var codePt, byte1;
var buffLen = array.length;
result.length = 0;
for (var i = 0; i < buffLen; ) {
byte1 = array[i++];
if (byte1 <= 0x7f) {
codePt = byte1;
} else if (byte1 <= 0xdf) {
codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
} else if (byte1 <= 0xef) {
codePt =
((byte1 & 0x0f) << 12) |
((array[i++] & 0x3f) << 6) |
(array[i++] & 0x3f);
} else if (String.fromCodePoint) {
codePt =
((byte1 & 0x07) << 18) |
((array[i++] & 0x3f) << 12) |
((array[i++] & 0x3f) << 6) |
(array[i++] & 0x3f);
} else {
codePt = 63; // Cannot convert four byte code points, so use "?" instead
i += 3;
}
result.push(
charCache[codePt] ||
(charCache[codePt] = charFromCodePt(codePt)),
);
}
return result.join('');
};
})();
export {
isIPv4,
isIPv6,
@@ -43,4 +90,5 @@ export {
getIfNotBlank,
isPresent,
getIfPresent,
utf8ArrayToStr,
};