Skip to content

Commit ad41e99

Browse files
committed
feat: 支持 Egern 输出
1 parent 7d8132d commit ad41e99

File tree

6 files changed

+312
-3
lines changed

6 files changed

+312
-3
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</div>
88

99
<p align="center" color="#6a737d">
10-
Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.
10+
Advanced Subscription Manager for QX, Loon, Surge, Stash, Egern and Shadowrocket.
1111
</p>
1212

1313
[![Build](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/sub-store-org/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/sub-store-org/Sub-Store) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/Peng-Ym/Sub-Store) ![Lines of code](https://img.shields.io/tokei/lines/github/sub-store-org/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/sub-store-org/Sub-Store)
@@ -49,6 +49,7 @@ Core functionalities:
4949
- [x] Surge
5050
- [x] SurgeMac(Use mihomo to support protocols that are not supported by Surge itself)
5151
- [x] Loon
52+
- [x] Egern
5253
- [x] Shadowrocket
5354
- [x] QX
5455
- [x] sing-box

backend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.14.423",
3+
"version": "2.14.424",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
55
"main": "src/main.js",
66
"scripts": {

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

+4
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ function URI_VMess() {
340340
} else if (params.net === 'h2' || proxy.network === 'h2') {
341341
proxy.network = 'h2';
342342
}
343+
// 暂不支持 tcp + host + path
344+
// else if (params.net === 'tcp' || proxy.network === 'tcp') {
345+
// proxy.network = 'tcp';
346+
// }
343347
if (proxy.network) {
344348
let transportHost = params.host ?? params.obfsParam;
345349
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
export default function Egern_Producer() {
2+
const type = 'ALL';
3+
const produce = (proxies, type, opts = {}) => {
4+
// https://egernapp.com/zh-CN/docs/configuration/proxies
5+
const list = proxies
6+
.filter((proxy) => {
7+
if (opts['include-unsupported-proxy']) return true;
8+
if (
9+
![
10+
'http',
11+
'socks5',
12+
'ss',
13+
'trojan',
14+
'hysteria2',
15+
'vless',
16+
'vmess',
17+
].includes(proxy.type) ||
18+
(proxy.type === 'ss' &&
19+
((proxy.plugin === 'obfs' &&
20+
!['http', 'tls'].includes(
21+
proxy['plugin-opts']?.mode,
22+
)) ||
23+
![
24+
'chacha20-ietf-poly1305',
25+
'chacha20-poly1305',
26+
'aes-256-gcm',
27+
'aes-128-gcm',
28+
'none',
29+
'tbale',
30+
'rc4',
31+
'rc4-md5',
32+
'aes-128-cfb',
33+
'aes-192-cfb',
34+
'aes-256-cfb',
35+
'aes-128-ctr',
36+
'aes-192-ctr',
37+
'aes-256-ctr',
38+
'bf-cfb',
39+
'camellia-128-cfb',
40+
'camellia-192-cfb',
41+
'camellia-256-cfb',
42+
'cast5-cfb',
43+
'des-cfb',
44+
'idea-cfb',
45+
'rc2-cfb',
46+
'seed-cfb',
47+
'salsa20',
48+
'chacha20',
49+
'chacha20-ietf',
50+
].includes(proxy.cipher))) ||
51+
(proxy.type === 'vmess' &&
52+
(![
53+
'auto',
54+
'aes-128-gcm',
55+
'chacha20-poly1305',
56+
'none',
57+
'zero',
58+
].includes(proxy.cipher) ||
59+
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
60+
proxy.network))) ||
61+
(proxy.type === 'trojan' &&
62+
!['http', 'ws', 'tcp'].includes(proxy.network) &&
63+
proxy.network) ||
64+
(proxy.type === 'vless' &&
65+
(typeof proxy.flow !== 'undefined' ||
66+
proxy['reality-opts'] ||
67+
(!['http', 'ws', 'tcp'].includes(proxy.network) &&
68+
proxy.network)))
69+
) {
70+
return false;
71+
}
72+
return true;
73+
})
74+
.map((proxy) => {
75+
if (proxy.type === 'http') {
76+
proxy = {
77+
type: 'http',
78+
name: proxy.name,
79+
server: proxy.server,
80+
port: proxy.port,
81+
username: proxy.username,
82+
password: proxy.password,
83+
tfo: proxy.tfo || proxy['fast-open'],
84+
next_hop: proxy.next_hop,
85+
};
86+
} else if (proxy.type === 'socks5') {
87+
proxy = {
88+
type: 'socks5',
89+
name: proxy.name,
90+
server: proxy.server,
91+
port: proxy.port,
92+
username: proxy.username,
93+
password: proxy.password,
94+
tfo: proxy.tfo || proxy['fast-open'],
95+
udp_relay:
96+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
97+
next_hop: proxy.next_hop,
98+
};
99+
} else if (proxy.type === 'ss') {
100+
proxy = {
101+
type: 'shadowsocks',
102+
name: proxy.name,
103+
method:
104+
proxy.cipher === 'chacha20-ietf-poly1305'
105+
? 'chacha20-poly1305'
106+
: proxy.cipher,
107+
server: proxy.server,
108+
port: proxy.port,
109+
password: proxy.password,
110+
tfo: proxy.tfo || proxy['fast-open'],
111+
udp_relay:
112+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
113+
next_hop: proxy.next_hop,
114+
};
115+
if (proxy.plugin === 'obfs') {
116+
proxy.obfs = proxy['plugin-opts'].mode;
117+
proxy.obfs_host = proxy['plugin-opts'].host;
118+
proxy.obfs_uri = proxy['plugin-opts'].path;
119+
}
120+
} else if (proxy.type === 'hysteria2') {
121+
proxy = {
122+
type: 'hysteria2',
123+
name: proxy.name,
124+
server: proxy.server,
125+
port: proxy.port,
126+
auth: proxy.password,
127+
tfo: proxy.tfo || proxy['fast-open'],
128+
udp_relay:
129+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
130+
next_hop: proxy.next_hop,
131+
sni: proxy.sni,
132+
skip_tls_verify: proxy['skip-cert-verify'],
133+
};
134+
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
135+
proxy.obfs = 'salamander';
136+
proxy.obfs_password = proxy['obfs-password'];
137+
}
138+
} else if (proxy.type === 'trojan') {
139+
if (proxy.network === 'ws') {
140+
proxy.websocket = {
141+
path: proxy['ws-opts']?.path,
142+
host: proxy['ws-opts']?.headers?.Host,
143+
};
144+
}
145+
proxy = {
146+
type: 'trojan',
147+
name: proxy.name,
148+
server: proxy.server,
149+
port: proxy.port,
150+
user_id: proxy.uuid,
151+
security: proxy.cipher,
152+
tfo: proxy.tfo || proxy['fast-open'],
153+
legacy: proxy.legacy,
154+
udp_relay:
155+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
156+
next_hop: proxy.next_hop,
157+
sni: proxy.sni,
158+
skip_tls_verify: proxy['skip-cert-verify'],
159+
websocket: proxy.websocket,
160+
};
161+
} else if (proxy.type === 'vmess') {
162+
if (proxy.network === 'ws') {
163+
proxy.transport = {
164+
[proxy.tls ? 'wss' : 'ws']: {
165+
path: proxy['ws-opts']?.path,
166+
headers: {
167+
Host: proxy['ws-opts']?.headers?.Host,
168+
},
169+
sni: proxy.tls ? proxy.sni : undefined,
170+
skip_tls_verify: proxy.tls
171+
? proxy['skip-cert-verify']
172+
: undefined,
173+
},
174+
};
175+
} else if (proxy.network === 'http') {
176+
proxy.transport = {
177+
http: {
178+
method: proxy['http-opts']?.method,
179+
path: proxy['http-opts']?.path,
180+
headers: {
181+
Host: Array.isArray(
182+
proxy['http-opts']?.headers?.Host,
183+
)
184+
? proxy['http-opts']?.headers?.Host[0]
185+
: proxy['http-opts']?.headers?.Host,
186+
},
187+
skip_tls_verify: proxy['skip-cert-verify'],
188+
},
189+
};
190+
} else if (proxy.network === 'tcp' || !proxy.network) {
191+
proxy.transport = {
192+
[proxy.tls ? 'tls' : 'tcp']: {
193+
sni: proxy.tls ? proxy.sni : undefined,
194+
skip_tls_verify: proxy.tls
195+
? proxy['skip-cert-verify']
196+
: undefined,
197+
},
198+
};
199+
}
200+
proxy = {
201+
type: 'vmess',
202+
name: proxy.name,
203+
server: proxy.server,
204+
port: proxy.port,
205+
user_id: proxy.uuid,
206+
security: proxy.cipher,
207+
tfo: proxy.tfo || proxy['fast-open'],
208+
legacy: proxy.legacy,
209+
udp_relay:
210+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
211+
next_hop: proxy.next_hop,
212+
transport: proxy.transport,
213+
// sni: proxy.sni,
214+
// skip_tls_verify: proxy['skip-cert-verify'],
215+
};
216+
} else if (proxy.type === 'vless') {
217+
if (proxy.network === 'ws') {
218+
proxy.transport = {
219+
[proxy.tls ? 'wss' : 'ws']: {
220+
path: proxy['ws-opts']?.path,
221+
headers: {
222+
Host: proxy['ws-opts']?.headers?.Host,
223+
},
224+
sni: proxy.tls ? proxy.sni : undefined,
225+
skip_tls_verify: proxy.tls
226+
? proxy['skip-cert-verify']
227+
: undefined,
228+
},
229+
};
230+
} else if (proxy.network === 'http') {
231+
proxy.transport = {
232+
http: {
233+
method: proxy['http-opts']?.method,
234+
path: proxy['http-opts']?.path,
235+
headers: {
236+
Host: Array.isArray(
237+
proxy['http-opts']?.headers?.Host,
238+
)
239+
? proxy['http-opts']?.headers?.Host[0]
240+
: proxy['http-opts']?.headers?.Host,
241+
},
242+
skip_tls_verify: proxy['skip-cert-verify'],
243+
},
244+
};
245+
} else if (proxy.network === 'tcp' || !proxy.network) {
246+
proxy.transport = {
247+
[proxy.tls ? 'tls' : 'tcp']: {
248+
sni: proxy.tls ? proxy.sni : undefined,
249+
skip_tls_verify: proxy.tls
250+
? proxy['skip-cert-verify']
251+
: undefined,
252+
},
253+
};
254+
}
255+
proxy = {
256+
type: 'vless',
257+
name: proxy.name,
258+
server: proxy.server,
259+
port: proxy.port,
260+
user_id: proxy.uuid,
261+
security: proxy.cipher,
262+
tfo: proxy.tfo || proxy['fast-open'],
263+
legacy: proxy.legacy,
264+
udp_relay:
265+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
266+
next_hop: proxy.next_hop,
267+
transport: proxy.transport,
268+
// sni: proxy.sni,
269+
// skip_tls_verify: proxy['skip-cert-verify'],
270+
};
271+
}
272+
273+
delete proxy.subName;
274+
delete proxy.collectionName;
275+
delete proxy.id;
276+
delete proxy.resolved;
277+
delete proxy['no-resolve'];
278+
if (type !== 'internal') {
279+
for (const key in proxy) {
280+
if (proxy[key] == null || /^_/i.test(key)) {
281+
delete proxy[key];
282+
}
283+
}
284+
}
285+
return {
286+
[proxy.type]: {
287+
...proxy,
288+
type: undefined,
289+
},
290+
};
291+
});
292+
return type === 'internal'
293+
? list
294+
: 'proxies:\n' +
295+
list
296+
.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n')
297+
.join('');
298+
};
299+
return { type, produce };
300+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import QX_Producer from './qx';
1010
import Shadowrocket_Producer from './shadowrocket';
1111
import Surfboard_Producer from './surfboard';
1212
import singbox_Producer from './sing-box';
13+
import Egern_Producer from './egern';
1314

1415
function JSON_Producer() {
1516
const type = 'ALL';
@@ -34,4 +35,5 @@ export default {
3435
ShadowRocket: Shadowrocket_Producer(),
3536
Surfboard: Surfboard_Producer(),
3637
'sing-box': singbox_Producer(),
38+
Egern: Egern_Producer(),
3739
};

backend/src/utils/user-agent.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export function getUserAgentFromHeaders(headers) {
1818
export function getPlatformFromUserAgent({ ua, UA, accept }) {
1919
if (UA.indexOf('Quantumult%20X') !== -1) {
2020
return 'QX';
21+
} else if (ua.indexOf('egern') !== -1) {
22+
return 'Egern';
2123
} else if (UA.indexOf('Surfboard') !== -1) {
2224
return 'Surfboard';
2325
} else if (UA.indexOf('Surge Mac') !== -1) {
@@ -39,7 +41,7 @@ export function getPlatformFromUserAgent({ ua, UA, accept }) {
3941
return 'ClashMeta';
4042
} else if (ua.indexOf('clash') !== -1) {
4143
return 'Clash';
42-
} else if (ua.indexOf('v2ray') !== -1 || ua.indexOf('egern') !== -1) {
44+
} else if (ua.indexOf('v2ray') !== -1) {
4345
return 'V2Ray';
4446
} else if (ua.indexOf('sing-box') !== -1) {
4547
return 'sing-box';

0 commit comments

Comments
 (0)