Skip to content

Commit 45ef59f

Browse files
committed
feat: 支持读取 Clash 订阅
1 parent 5cc52f1 commit 45ef59f

15 files changed

+500
-78
lines changed

lib/class/BlackSSLProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ export default class BlackSSLProvider extends Provider {
3131
}
3232

3333
public getNodeList(): ReturnType<typeof getBlackSSLConfig> {
34-
return getBlackSSLConfig(this);
34+
return getBlackSSLConfig(this.username, this.password);
3535
}
3636
}

lib/class/ClashProvider.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import Joi from '@hapi/joi';
2+
import assert from 'assert';
3+
import axios from 'axios';
4+
import chalk from 'chalk';
5+
import yaml from 'yaml';
6+
import _ from 'lodash';
7+
8+
import {
9+
ClashProviderConfig,
10+
HttpsNodeConfig,
11+
NodeTypeEnum,
12+
ShadowsocksNodeConfig,
13+
ShadowsocksrNodeConfig,
14+
SnellNodeConfig,
15+
VmessNodeConfig,
16+
} from '../types';
17+
import { ConfigCache } from '../utils';
18+
import { NETWORK_TIMEOUT } from '../utils/constant';
19+
import Provider from './Provider';
20+
21+
type SupportConfigTypes = ShadowsocksNodeConfig|VmessNodeConfig|HttpsNodeConfig|ShadowsocksrNodeConfig|SnellNodeConfig;
22+
23+
export default class ClashProvider extends Provider {
24+
public static async getClashSubscription(url: string): Promise<ReadonlyArray<SupportConfigTypes>> {
25+
assert(url, '未指定订阅地址 url');
26+
27+
return ConfigCache.has(url) ?
28+
ConfigCache.get(url) :
29+
await requestConfigFromRemote(url);
30+
}
31+
32+
public readonly url: string;
33+
34+
constructor(config: ClashProviderConfig) {
35+
super(config);
36+
37+
const schema = Joi.object({
38+
url: Joi
39+
.string()
40+
.uri({
41+
scheme: [
42+
/https?/,
43+
],
44+
})
45+
.required(),
46+
})
47+
.unknown();
48+
49+
const { error } = schema.validate(config);
50+
51+
if (error) {
52+
throw error;
53+
}
54+
55+
this.url = config.url;
56+
}
57+
58+
public getNodeList(): ReturnType<typeof ClashProvider.getClashSubscription> {
59+
return ClashProvider.getClashSubscription(this.url);
60+
}
61+
}
62+
63+
async function requestConfigFromRemote(url): Promise<ReadonlyArray<SupportConfigTypes>> {
64+
const response = await axios.get(url, {
65+
timeout: NETWORK_TIMEOUT,
66+
responseType: 'text',
67+
});
68+
const clashConfig = yaml.parse(response.data);
69+
const proxyList: any[] = clashConfig.Proxy;
70+
const result = proxyList.map<SupportConfigTypes>(item => {
71+
switch (item.type) {
72+
case 'ss':
73+
if (item.plugin && item.plugin !== 'obfs') {
74+
console.log();
75+
console.log(`不支持读取 ${item.plugin} 类型的 Clash 节点,节点 ${item.name} 会被省略`);
76+
return null;
77+
}
78+
79+
return {
80+
type: NodeTypeEnum.Shadowsocks,
81+
nodeName: item.name,
82+
hostname: item.server,
83+
port: item.port,
84+
method: item.cipher,
85+
password: item.password,
86+
'udp-relay': item.udp ? 'true' : 'false',
87+
...(item.plugin && item.plugin === 'obfs' ? {
88+
obfs: item['plugin-opts'].mode,
89+
'obfs-host': item['plugin-opts'].host || 'www.bing.com',
90+
} : null),
91+
};
92+
93+
case 'vmess':
94+
return {
95+
type: NodeTypeEnum.Vmess,
96+
nodeName: item.name,
97+
hostname: item.server,
98+
port: item.port,
99+
uuid: item.uuid,
100+
alterId: item.alterId ? `${item.alterId}` : '0',
101+
method: item.cipher || 'auto',
102+
udp: item.udp !== void 0 ? item.udp : false,
103+
tls: item.tls !== void 0 ? item.tls : false,
104+
network: item.network || 'tcp',
105+
...(item.network === 'ws' ? {
106+
path: _.get(item, 'ws-path', '/'),
107+
host: _.get(item, 'ws-headers.Host', ''),
108+
} : null),
109+
};
110+
111+
case 'http':
112+
if (item.tls !== 'https') {
113+
console.log();
114+
console.log(`不支持读取 HTTP 类型的 Clash 节点,节点 ${item.name} 会被省略`);
115+
return null;
116+
}
117+
118+
return {
119+
type: NodeTypeEnum.HTTPS,
120+
nodeName: item.name,
121+
hostname: item.server,
122+
port: item.port,
123+
username: item.username || '',
124+
password: item.password || '',
125+
};
126+
127+
case 'snell':
128+
return {
129+
type: NodeTypeEnum.Snell,
130+
nodeName: item.name,
131+
hostname: item.server,
132+
port: item.port,
133+
psk: item.psk,
134+
obfs: _.get(item, 'obfs-opts.mode', 'http'),
135+
};
136+
137+
case 'ssr':
138+
return {
139+
type: NodeTypeEnum.Shadowsocksr,
140+
nodeName: item.name,
141+
hostname: item.server,
142+
port: item.port,
143+
password: item.password,
144+
obfs: item.obfs,
145+
obfsparam: item.obfsparam,
146+
protocol: item.protocol,
147+
protoparam: item.protocolparam,
148+
method: item.cipher,
149+
};
150+
151+
default:
152+
console.log();
153+
console.log(chalk.yellow(`不支持读取 ${item.type} 的节点,节点 ${item.name} 会被省略`));
154+
return null;
155+
}
156+
})
157+
.filter(item => !!item);
158+
159+
ConfigCache.set(url, result);
160+
161+
return result;
162+
}

lib/class/ShadowsocksJsonSubscribeProvider.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ export default class ShadowsocksJsonSubscribeProvider extends Provider {
3434
}
3535

3636
public getNodeList(): ReturnType<typeof getShadowsocksJSONConfig> {
37-
return getShadowsocksJSONConfig({
38-
url: this.url,
39-
udpRelay: this.udpRelay,
40-
});
37+
return getShadowsocksJSONConfig(this.url, this.udpRelay);
4138
}
4239
}

lib/class/ShadowsocksSubscribeProvider.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ export default class ShadowsocksSubscribeProvider extends Provider {
3434
}
3535

3636
public getNodeList(): ReturnType<typeof getShadowsocksSubscription> {
37-
return getShadowsocksSubscription({
38-
url: this.url,
39-
udpRelay: this.udpRelay,
40-
});
37+
return getShadowsocksSubscription(this.url, this.udpRelay);
4138
}
4239
}

lib/class/ShadowsocksrSubscribeProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ export default class ShadowsocksrSubscribeProvider extends Provider {
3131
}
3232

3333
public getNodeList(): ReturnType<typeof getShadowsocksrSubscription> {
34-
return getShadowsocksrSubscription(this);
34+
return getShadowsocksrSubscription(this.url);
3535
}
3636
}

lib/class/V2rayNSubscribeProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ export default class V2rayNSubscribeProvider extends Provider {
3131
}
3232

3333
public getNodeList(): ReturnType<typeof getV2rayNSubscription> {
34-
return getV2rayNSubscription(this);
34+
return getV2rayNSubscription(this.url);
3535
}
3636
}

lib/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Provider from './class/Provider';
2+
13
export enum NodeTypeEnum {
24
HTTPS = 'https',
35
Shadowsocks = 'shadowsocks',
@@ -13,6 +15,7 @@ export enum SupportProviderEnum {
1315
ShadowsocksSubscribe = 'shadowsocks_subscribe',
1416
ShadowsocksrSubscribe = 'shadowsocksr_subscribe',
1517
Custom = 'custom',
18+
Clash = 'clash',
1619
}
1720

1821
export interface CommandConfig {
@@ -98,6 +101,10 @@ export interface V2rayNSubscribeProviderConfig extends ProviderConfig {
98101
readonly url: string;
99102
}
100103

104+
export interface ClashProviderConfig extends ProviderConfig {
105+
readonly url: string;
106+
}
107+
101108
export interface CustomProviderConfig extends ProviderConfig {
102109
readonly nodeList: ReadonlyArray<PossibleNodeConfigType>;
103110
}
@@ -150,13 +157,15 @@ export interface VmessNodeConfig extends SimpleNodeConfig {
150157
readonly alterId: string;
151158
readonly network: 'tcp' | 'kcp' | 'ws' | 'http' ;
152159
readonly tls: boolean;
153-
readonly host: string;
154-
readonly path: string;
160+
readonly host?: string;
161+
readonly path?: string;
162+
readonly udp?: boolean;
155163
}
156164

157165
export interface SimpleNodeConfig {
158166
readonly type: NodeTypeEnum;
159167
readonly enable?: boolean;
168+
readonly tfo?: boolean; // TCP Fast Open
160169
nodeName: string; // tslint:disable-line
161170
binPath?: string; // tslint:disable-line
162171
localPort?: number; // tslint:disable-line

lib/utils/constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const NETWORK_TIMEOUT = process.env.SURGIO_NETWORK_TIMEOUT ? Number(process.env.SURGIO_NETWORK_TIMEOUT) : 20000;
2+
export const OBFS_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148';

lib/utils/get-provider.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import assert from "assert";
22

33
import BlackSSLProvider from '../class/BlackSSLProvider';
4+
import ClashProvider from '../class/ClashProvider';
45
import CustomProvider from '../class/CustomProvider';
56
import ShadowsocksJsonSubscribeProvider from '../class/ShadowsocksJsonSubscribeProvider';
67
import ShadowsocksrSubscribeProvider from '../class/ShadowsocksrSubscribeProvider';
78
import ShadowsocksSubscribeProvider from '../class/ShadowsocksSubscribeProvider';
89
import V2rayNSubscribeProvider from '../class/V2rayNSubscribeProvider';
910
import { SupportProviderEnum } from '../types';
1011

11-
export default function(config: any): BlackSSLProvider|ShadowsocksJsonSubscribeProvider|ShadowsocksSubscribeProvider|CustomProvider|V2rayNSubscribeProvider|ShadowsocksrSubscribeProvider {
12+
export default function(config: any): BlackSSLProvider|ShadowsocksJsonSubscribeProvider|ShadowsocksSubscribeProvider|CustomProvider|V2rayNSubscribeProvider|ShadowsocksrSubscribeProvider|ClashProvider {
1213
switch (config.type) {
1314
case SupportProviderEnum.BlackSSL:
1415
return new BlackSSLProvider(config);
@@ -29,6 +30,9 @@ export default function(config: any): BlackSSLProvider|ShadowsocksJsonSubscribeP
2930
case SupportProviderEnum.V2rayNSubscribe:
3031
return new V2rayNSubscribeProvider(config);
3132

33+
case SupportProviderEnum.Clash:
34+
return new ClashProvider(config);
35+
3236
default:
3337
throw new Error(`Unsupported provider type: ${config.type}`);
3438
}

0 commit comments

Comments
 (0)