Skip to content

Commit fe3da25

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

File tree

6 files changed

+310
-3
lines changed

6 files changed

+310
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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.425",
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

Lines changed: 4 additions & 0 deletions
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 {
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
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+
password: proxy.password,
151+
tfo: proxy.tfo || proxy['fast-open'],
152+
udp_relay:
153+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
154+
next_hop: proxy.next_hop,
155+
sni: proxy.sni,
156+
skip_tls_verify: proxy['skip-cert-verify'],
157+
websocket: proxy.websocket,
158+
};
159+
} else if (proxy.type === 'vmess') {
160+
if (proxy.network === 'ws') {
161+
proxy.transport = {
162+
[proxy.tls ? 'wss' : 'ws']: {
163+
path: proxy['ws-opts']?.path,
164+
headers: {
165+
Host: proxy['ws-opts']?.headers?.Host,
166+
},
167+
sni: proxy.tls ? proxy.sni : undefined,
168+
skip_tls_verify: proxy.tls
169+
? proxy['skip-cert-verify']
170+
: undefined,
171+
},
172+
};
173+
} else if (proxy.network === 'http') {
174+
proxy.transport = {
175+
http: {
176+
method: proxy['http-opts']?.method,
177+
path: proxy['http-opts']?.path,
178+
headers: {
179+
Host: Array.isArray(
180+
proxy['http-opts']?.headers?.Host,
181+
)
182+
? proxy['http-opts']?.headers?.Host[0]
183+
: proxy['http-opts']?.headers?.Host,
184+
},
185+
skip_tls_verify: proxy['skip-cert-verify'],
186+
},
187+
};
188+
} else if (proxy.network === 'tcp' || !proxy.network) {
189+
proxy.transport = {
190+
[proxy.tls ? 'tls' : 'tcp']: {
191+
sni: proxy.tls ? proxy.sni : undefined,
192+
skip_tls_verify: proxy.tls
193+
? proxy['skip-cert-verify']
194+
: undefined,
195+
},
196+
};
197+
}
198+
proxy = {
199+
type: 'vmess',
200+
name: proxy.name,
201+
server: proxy.server,
202+
port: proxy.port,
203+
user_id: proxy.uuid,
204+
security: proxy.cipher,
205+
tfo: proxy.tfo || proxy['fast-open'],
206+
legacy: proxy.legacy,
207+
udp_relay:
208+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
209+
next_hop: proxy.next_hop,
210+
transport: proxy.transport,
211+
// sni: proxy.sni,
212+
// skip_tls_verify: proxy['skip-cert-verify'],
213+
};
214+
} else if (proxy.type === 'vless') {
215+
if (proxy.network === 'ws') {
216+
proxy.transport = {
217+
[proxy.tls ? 'wss' : 'ws']: {
218+
path: proxy['ws-opts']?.path,
219+
headers: {
220+
Host: proxy['ws-opts']?.headers?.Host,
221+
},
222+
sni: proxy.tls ? proxy.sni : undefined,
223+
skip_tls_verify: proxy.tls
224+
? proxy['skip-cert-verify']
225+
: undefined,
226+
},
227+
};
228+
} else if (proxy.network === 'http') {
229+
proxy.transport = {
230+
http: {
231+
method: proxy['http-opts']?.method,
232+
path: proxy['http-opts']?.path,
233+
headers: {
234+
Host: Array.isArray(
235+
proxy['http-opts']?.headers?.Host,
236+
)
237+
? proxy['http-opts']?.headers?.Host[0]
238+
: proxy['http-opts']?.headers?.Host,
239+
},
240+
skip_tls_verify: proxy['skip-cert-verify'],
241+
},
242+
};
243+
} else if (proxy.network === 'tcp' || !proxy.network) {
244+
proxy.transport = {
245+
[proxy.tls ? 'tls' : 'tcp']: {
246+
sni: proxy.tls ? proxy.sni : undefined,
247+
skip_tls_verify: proxy.tls
248+
? proxy['skip-cert-verify']
249+
: undefined,
250+
},
251+
};
252+
}
253+
proxy = {
254+
type: 'vless',
255+
name: proxy.name,
256+
server: proxy.server,
257+
port: proxy.port,
258+
user_id: proxy.uuid,
259+
security: proxy.cipher,
260+
tfo: proxy.tfo || proxy['fast-open'],
261+
legacy: proxy.legacy,
262+
udp_relay:
263+
proxy.udp || proxy.udp_relay || proxy.udp_relay,
264+
next_hop: proxy.next_hop,
265+
transport: proxy.transport,
266+
// sni: proxy.sni,
267+
// skip_tls_verify: proxy['skip-cert-verify'],
268+
};
269+
}
270+
271+
delete proxy.subName;
272+
delete proxy.collectionName;
273+
delete proxy.id;
274+
delete proxy.resolved;
275+
delete proxy['no-resolve'];
276+
if (type !== 'internal') {
277+
for (const key in proxy) {
278+
if (proxy[key] == null || /^_/i.test(key)) {
279+
delete proxy[key];
280+
}
281+
}
282+
}
283+
return {
284+
[proxy.type]: {
285+
...proxy,
286+
type: undefined,
287+
},
288+
};
289+
});
290+
return type === 'internal'
291+
? list
292+
: 'proxies:\n' +
293+
list
294+
.map((proxy) => ' - ' + JSON.stringify(proxy) + '\n')
295+
.join('');
296+
};
297+
return { type, produce };
298+
}

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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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)