Skip to content

Commit c073870

Browse files
committed
perf: Add support for sending http requests using specific nodes
Only supported on Loon & Surge
1 parent e933320 commit c073870

File tree

5 files changed

+256
-2
lines changed

5 files changed

+256
-2
lines changed

backend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.12.4",
3+
"version": "2.12.5",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
55
"main": "src/main.js",
66
"scripts": {
@@ -19,6 +19,7 @@
1919
"js-base64": "^3.7.2",
2020
"lodash": "^4.17.21",
2121
"request": "^2.88.2",
22+
"requests": "^0.3.0",
2223
"semver": "^7.3.7",
2324
"static-js-yaml": "^1.0.0",
2425
"uuid": "^8.3.2"

backend/pnpm-lock.yaml

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/src/core/proxy-utils/processors/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getFlag } from '@/utils/geo';
55
import lodash from 'lodash';
66
import $ from '@/core/app';
77
import { hex_md5 } from '@/vendor/md5';
8+
import {ProxyUtils} from '@/core/proxy-utils';
89

910
/**
1011
The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows:
@@ -631,6 +632,7 @@ function createDynamicFunction(name, script, $arguments) {
631632
'$persistentStore',
632633
'$httpClient',
633634
'$notification',
635+
'ProxyUtils',
634636
`${script}\n return ${name}`,
635637
)(
636638
$arguments,
@@ -642,13 +644,15 @@ function createDynamicFunction(name, script, $arguments) {
642644
$httpClient,
643645
// eslint-disable-next-line no-undef
644646
$notification,
647+
ProxyUtils
645648
);
646649
} else {
647650
return new Function(
648651
'$arguments',
649652
'$substore',
650653
'lodash',
654+
'ProxyUtils',
651655
`${script}\n return ${name}`,
652-
)($arguments, $, lodash);
656+
)($arguments, $, lodash, ProxyUtils);
653657
}
654658
}

backend/src/vendor/open-api.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,17 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
253253

254254
events.onRequest(method, options);
255255

256+
if (options.node) {
257+
// Surge & Loon allow connecting to a server using a specified proxy node
258+
if (isSurge) {
259+
const build = $environment['surge-build'];
260+
if (build && parseInt(build) >= 2407) {
261+
options['policy-descriptor'] = options.node;
262+
delete options.node;
263+
}
264+
}
265+
}
266+
256267
let worker;
257268
if (isQX) {
258269
worker = $task.fetch({

scripts/ip-flag.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
2+
const CACHE_EXPIRATION_TIME_MS = 10 * 60 * 1000;
3+
const $ = $substore;
4+
5+
class ResourceCache {
6+
constructor(expires) {
7+
this.expires = expires;
8+
if (!$.read(RESOURCE_CACHE_KEY)) {
9+
$.write('{}', RESOURCE_CACHE_KEY);
10+
}
11+
this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY));
12+
this._cleanup();
13+
}
14+
15+
_cleanup() {
16+
// clear obsolete cached resource
17+
let clear = false;
18+
Object.entries(this.resourceCache).forEach((entry) => {
19+
const [id, updated] = entry;
20+
if (new Date().getTime() - updated > this.expires) {
21+
$.delete(`#${id}`);
22+
delete this.resourceCache[id];
23+
clear = true;
24+
}
25+
});
26+
if (clear) this._persist();
27+
}
28+
29+
revokeAll() {
30+
Object.keys(this.resourceCache).forEach((id) => {
31+
$.delete(`#${id}`);
32+
});
33+
this.resourceCache = {};
34+
this._persist();
35+
}
36+
37+
_persist() {
38+
$.write(JSON.stringify(this.resourceCache), RESOURCE_CACHE_KEY);
39+
}
40+
41+
get(id) {
42+
const updated = this.resourceCache[id];
43+
if (updated && new Date().getTime() - updated <= this.expires) {
44+
return $.read(`#${id}`);
45+
}
46+
return null;
47+
}
48+
49+
set(id, value) {
50+
this.resourceCache[id] = new Date().getTime();
51+
this._persist();
52+
$.write(value, `#${id}`);
53+
}
54+
}
55+
56+
const resourceCache = new ResourceCache(CACHE_EXPIRATION_TIME_MS);
57+
58+
async function operator(proxies) {
59+
const { isLoon, isSurge } = $substore.env;
60+
let support = true;
61+
if (isLoon) {
62+
support = true;
63+
} else if (isSurge) {
64+
const build = $environment['surge-build'];
65+
if (build && parseInt(build) >= 2407) {
66+
support = true;
67+
}
68+
}
69+
70+
if (support) {
71+
await Promise.all(proxies.map(async proxy => {
72+
try {
73+
// remove the original flag
74+
let proxyName = removeFlag(proxy.name);
75+
76+
// query ip-api
77+
const countryCode = await queryIpApi(proxy);
78+
79+
proxyName = getFlagEmoji(countryCode) + ' ' + proxyName;
80+
proxy.name = proxyName;
81+
} catch (err) {
82+
// TODO:
83+
}
84+
}));
85+
} else {
86+
$.error(`IP Flag only supports Loon and Surge!`);
87+
}
88+
return proxies;
89+
}
90+
91+
const tasks = new Map();
92+
async function queryIpApi(proxy) {
93+
const id = getId(proxy);
94+
if (tasks.has(id)) {
95+
return tasks.get(id);
96+
}
97+
98+
const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:78.0) Gecko/20100101 Firefox/78.0";
99+
const headers = {
100+
"User-Agent": ua
101+
};
102+
const { isLoon } = $substore.env;
103+
const target = isLoon ? "Loon" : "Surge";
104+
const result = new Promise((resolve, reject) => {
105+
const cached = resourceCache.get(id);
106+
if (cached) {
107+
resolve(cached);
108+
}
109+
const url = `http://ip-api.com/json`;
110+
const node = ProxyUtils.produce([proxy], target);
111+
$.http.get({
112+
url,
113+
headers,
114+
node
115+
}).then(resp => {
116+
const body = resp.body;
117+
const data = JSON.parse(body);
118+
if (data.status === "success") {
119+
resourceCache.set(id, data.countryCode);
120+
resolve(data.countryCode);
121+
} else {
122+
reject(new Error(data.message));
123+
}
124+
}).catch(err => {
125+
console.log(err);
126+
reject(err);
127+
});
128+
});
129+
tasks.set(id, result);
130+
return result;
131+
}
132+
133+
function getId(proxy) {
134+
return MD5(`IP-FLAG-${proxy.server}-${proxy.port}`);
135+
}
136+
137+
function getFlagEmoji(countryCode) {
138+
const codePoints = countryCode
139+
.toUpperCase()
140+
.split('')
141+
.map(char => 127397 + char.charCodeAt());
142+
return String.fromCodePoint(...codePoints);
143+
}
144+
145+
function removeFlag(str) {
146+
return str
147+
.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, '')
148+
.trim();
149+
}
150+
151+
var MD5 = function (d) { var r = M(V(Y(X(d), 8 * d.length))); return r.toLowerCase() }; function M(d) { for (var _, m = "0123456789ABCDEF", f = "", r = 0; r < d.length; r++)_ = d.charCodeAt(r), f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _); return f } function X(d) { for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++)_[m] = 0; for (m = 0; m < 8 * d.length; m += 8)_[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32; return _ } function V(d) { for (var _ = "", m = 0; m < 32 * d.length; m += 8)_ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255); return _ } function Y(d, _) { d[_ >> 5] |= 128 << _ % 32, d[14 + (_ + 64 >>> 9 << 4)] = _; for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) { var h = m, t = f, g = r, e = i; f = md5_ii(f = md5_ii(f = md5_ii(f = md5_ii(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_ff(f = md5_ff(f = md5_ff(f = md5_ff(f, r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = safe_add(m, h), f = safe_add(f, t), r = safe_add(r, g), i = safe_add(i, e) } return Array(m, f, r, i) } function md5_cmn(d, _, m, f, r, i) { return safe_add(bit_rol(safe_add(safe_add(_, d), safe_add(f, i)), r), m) } function md5_ff(d, _, m, f, r, i, n) { return md5_cmn(_ & m | ~_ & f, d, _, r, i, n) } function md5_gg(d, _, m, f, r, i, n) { return md5_cmn(_ & f | m & ~f, d, _, r, i, n) } function md5_hh(d, _, m, f, r, i, n) { return md5_cmn(_ ^ m ^ f, d, _, r, i, n) } function md5_ii(d, _, m, f, r, i, n) { return md5_cmn(m ^ (_ | ~f), d, _, r, i, n) } function safe_add(d, _) { var m = (65535 & d) + (65535 & _); return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m } function bit_rol(d, _) { return d << _ | d >>> 32 - _ }

0 commit comments

Comments
 (0)