x3ui-api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/package.json +23 -0
- package/src/index.d.ts +293 -0
- package/src/index.js +415 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "x3ui-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "API client for x3ui panel",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "jest"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["x3ui", "api", "client"],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"axios": "^1.6.5",
|
|
15
|
+
"form-data": "^4.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.11.5"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"url": "https://github.com/RedGuys/x3ui-api"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import {AxiosInstance} from 'axios';
|
|
2
|
+
|
|
3
|
+
export interface X3UIConfig {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
parseJSONSettings?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LoginResponse {
|
|
10
|
+
success: boolean;
|
|
11
|
+
msg: string;
|
|
12
|
+
obj: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface InboundConfig {
|
|
16
|
+
id?: number;
|
|
17
|
+
up: number;
|
|
18
|
+
down: number;
|
|
19
|
+
total: number;
|
|
20
|
+
remark: string;
|
|
21
|
+
enable: boolean;
|
|
22
|
+
expiryTime: number | null;
|
|
23
|
+
clientStats: ClientStats[];
|
|
24
|
+
listen: string;
|
|
25
|
+
port: number;
|
|
26
|
+
protocol: string;
|
|
27
|
+
settings: string | InboundSettings;
|
|
28
|
+
streamSettings: string | StreamSettings;
|
|
29
|
+
tag: string;
|
|
30
|
+
sniffing: string | SniffingSettings;
|
|
31
|
+
allocate: string | AllocateSettings;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AllocateSettings {
|
|
35
|
+
strategy: string;
|
|
36
|
+
refresh: number;
|
|
37
|
+
concurrency: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SniffingSettings {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
destOverride: string[];
|
|
43
|
+
metadataOnly: boolean;
|
|
44
|
+
routeOnly: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StreamSettings {
|
|
48
|
+
network: string;
|
|
49
|
+
security: string;
|
|
50
|
+
externalProxy: any[];
|
|
51
|
+
realitySettings: RealitySettings;
|
|
52
|
+
tcpSettings?: TCPSettings;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TCPSettings {
|
|
56
|
+
acceptProxyProtocol: boolean;
|
|
57
|
+
header: {
|
|
58
|
+
type: string;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RealitySettings {
|
|
63
|
+
show: boolean;
|
|
64
|
+
xver: number;
|
|
65
|
+
dest: string;
|
|
66
|
+
serverNames: string[];
|
|
67
|
+
privateKey: string;
|
|
68
|
+
minClient: string;
|
|
69
|
+
maxClient: string;
|
|
70
|
+
maxTimediff: number;
|
|
71
|
+
shortIds: string[];
|
|
72
|
+
settings: {
|
|
73
|
+
publicKey: string;
|
|
74
|
+
fingerprint: string;
|
|
75
|
+
serverName: string;
|
|
76
|
+
spiderX: string;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface InboundSettings {
|
|
81
|
+
clients: ClientSettings[];
|
|
82
|
+
decryption: string;
|
|
83
|
+
fallbacks: any[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ClientSettings {
|
|
87
|
+
id: string;
|
|
88
|
+
flow: string;
|
|
89
|
+
email: string;
|
|
90
|
+
limitIp: number;
|
|
91
|
+
totalGB: number;
|
|
92
|
+
expiryTime: number | null;
|
|
93
|
+
enable: boolean;
|
|
94
|
+
tgId: string;
|
|
95
|
+
subId: string;
|
|
96
|
+
reset: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ClientStats {
|
|
100
|
+
id: number;
|
|
101
|
+
inboundId: number;
|
|
102
|
+
enable: boolean;
|
|
103
|
+
email: string;
|
|
104
|
+
up: number;
|
|
105
|
+
down: number;
|
|
106
|
+
expiryTime: number | null;
|
|
107
|
+
total: number;
|
|
108
|
+
reset: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface SystemStats {
|
|
112
|
+
cpu: number;
|
|
113
|
+
cpuCores: number;
|
|
114
|
+
logicalPro: number;
|
|
115
|
+
cpuSpeedMhz: number;
|
|
116
|
+
mem: {
|
|
117
|
+
current: number;
|
|
118
|
+
total: number;
|
|
119
|
+
};
|
|
120
|
+
swap: {
|
|
121
|
+
current: number;
|
|
122
|
+
total: number;
|
|
123
|
+
}
|
|
124
|
+
disk: {
|
|
125
|
+
current: number;
|
|
126
|
+
total: number;
|
|
127
|
+
};
|
|
128
|
+
xray: {
|
|
129
|
+
state: boolean;
|
|
130
|
+
errorMsg: string;
|
|
131
|
+
version: string;
|
|
132
|
+
};
|
|
133
|
+
uptime: number;
|
|
134
|
+
loads: number[];
|
|
135
|
+
tcpCount: number;
|
|
136
|
+
udpCount: number;
|
|
137
|
+
netIO: {
|
|
138
|
+
up: number;
|
|
139
|
+
down: number;
|
|
140
|
+
};
|
|
141
|
+
netTraffic: {
|
|
142
|
+
up: number;
|
|
143
|
+
down: number;
|
|
144
|
+
};
|
|
145
|
+
publicIP: {
|
|
146
|
+
ipv4: string;
|
|
147
|
+
ipv6: string;
|
|
148
|
+
}
|
|
149
|
+
appStats: {
|
|
150
|
+
threads: number;
|
|
151
|
+
mem: number;
|
|
152
|
+
uptime: number;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface RealityInboundOptions {
|
|
157
|
+
remark: string;
|
|
158
|
+
port?: number;
|
|
159
|
+
dest?: string;
|
|
160
|
+
serverNames?: string[];
|
|
161
|
+
privateKey?: string;
|
|
162
|
+
publicKey?: string;
|
|
163
|
+
shortIds?: string[];
|
|
164
|
+
fingerprint?: string;
|
|
165
|
+
listenIP?: string;
|
|
166
|
+
expiryTime?: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ClientOptions {
|
|
170
|
+
id?: string;
|
|
171
|
+
email?: string;
|
|
172
|
+
totalGB?: number;
|
|
173
|
+
expiryTime?: number;
|
|
174
|
+
tgId?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ClientBuilder {
|
|
178
|
+
/**
|
|
179
|
+
* Set client UUID. If not provided, will be auto-generated
|
|
180
|
+
*/
|
|
181
|
+
setId(id: string): this;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Set client email
|
|
185
|
+
*/
|
|
186
|
+
setEmail(email: string): this;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set total traffic limit in GB
|
|
190
|
+
*/
|
|
191
|
+
setTotalGB(gb: number): this;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Set expiry time in unix timestamp
|
|
195
|
+
*/
|
|
196
|
+
setExpiryTime(timestamp: number): this;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Set Telegram ID
|
|
200
|
+
*/
|
|
201
|
+
setTgId(id: string): this;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build client configuration
|
|
205
|
+
*/
|
|
206
|
+
build(): Promise<ClientSettings>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface RealityBuilder {
|
|
210
|
+
/**
|
|
211
|
+
* Set the port for the inbound. If not provided, will auto-generate unused port
|
|
212
|
+
*/
|
|
213
|
+
setPort(port: number): this;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Set the remark/name for the inbound
|
|
217
|
+
*/
|
|
218
|
+
setRemark(remark: string): this;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Set the destination address (e.g. "yahoo.com:443")
|
|
222
|
+
*/
|
|
223
|
+
setDest(dest: string): this;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Set server names for SNI
|
|
227
|
+
*/
|
|
228
|
+
setServerNames(names: string[]): this;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Set Reality keypair. If not provided, will be auto-generated
|
|
232
|
+
*/
|
|
233
|
+
setKeyPair(privateKey: string, publicKey: string): this;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set short IDs for Reality. If not provided, random ones will be generated
|
|
237
|
+
*/
|
|
238
|
+
setShortIds(ids: string[]): this;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Set browser fingerprint. Defaults to "chrome"
|
|
242
|
+
*/
|
|
243
|
+
setFingerprint(fingerprint: string): this;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Set listen IP address. Defaults to empty string
|
|
247
|
+
*/
|
|
248
|
+
setListenIP(ip: string): this;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Set inbound expiry time. Defaults to 0 (no expiry)
|
|
252
|
+
*/
|
|
253
|
+
setExpiryTime(timestamp: number): this;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Add a new client to the inbound
|
|
257
|
+
*/
|
|
258
|
+
addClient(options?: Partial<ClientSettings>): ClientBuilder;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Build the final inbound config
|
|
262
|
+
*/
|
|
263
|
+
build(): Promise<InboundConfig>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export default class X3UIClient {
|
|
267
|
+
private client: AxiosInstance;
|
|
268
|
+
|
|
269
|
+
constructor(config: X3UIConfig);
|
|
270
|
+
|
|
271
|
+
login(username: string, password: string): Promise<LoginResponse>;
|
|
272
|
+
|
|
273
|
+
getSystemStats(): Promise<SystemStats>;
|
|
274
|
+
|
|
275
|
+
getInbounds(): Promise<InboundConfig[]>;
|
|
276
|
+
|
|
277
|
+
addInbound(config: Omit<InboundConfig, 'id'>): Promise<InboundConfig>;
|
|
278
|
+
|
|
279
|
+
updateInbound(id: number, config: Partial<InboundConfig>): Promise<void>;
|
|
280
|
+
|
|
281
|
+
deleteInbound(id: number): Promise<void>;
|
|
282
|
+
|
|
283
|
+
getInboundTraffic(id: number): Promise<{ up: number; down: number }>;
|
|
284
|
+
|
|
285
|
+
resetInboundTraffic(id: number): Promise<void>;
|
|
286
|
+
|
|
287
|
+
getNewX25519Cert(): Promise<{ privateKey: string; publicKey: string }>;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Create a new Reality inbound builder
|
|
291
|
+
*/
|
|
292
|
+
createRealityBuilder(options?: Partial<InboundConfig>): RealityBuilder;
|
|
293
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
module.exports = class X3UIClient {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.parseJSONSettings = config.parseJSONSettings || true;
|
|
7
|
+
this.client = axios.create({
|
|
8
|
+
baseURL: config.baseURL,
|
|
9
|
+
headers: config.token ? {
|
|
10
|
+
'Cookie': `lang=en-US; 3x-ui=${config.token}`,
|
|
11
|
+
} : {
|
|
12
|
+
'Cookie': 'lang=en-US'
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Login to the x3ui panel
|
|
19
|
+
* @param {string} username Panel username
|
|
20
|
+
* @param {string} password Panel password
|
|
21
|
+
* @returns {Promise<{success: boolean, msg: string, obj: any}>} Login response with success status and token
|
|
22
|
+
*/
|
|
23
|
+
async login(username, password) {
|
|
24
|
+
const formData = new URLSearchParams();
|
|
25
|
+
formData.append('username', username);
|
|
26
|
+
formData.append('password', password);
|
|
27
|
+
|
|
28
|
+
const response = await this.client.post('/login', formData, {
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (response.data.success) {
|
|
35
|
+
this.client.defaults.headers.Cookie = `lang=en-US; ${response.headers.get("set-cookie")[0].split(";")[0]}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getSystemStats() {
|
|
42
|
+
const response = await this.client.post('/server/status');
|
|
43
|
+
return response.data.obj;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all inbounds
|
|
48
|
+
* @returns {Promise<Array<{
|
|
49
|
+
* id?: number,
|
|
50
|
+
* up: number,
|
|
51
|
+
* down: number,
|
|
52
|
+
* total: number,
|
|
53
|
+
* remark: string,
|
|
54
|
+
* enable: boolean,
|
|
55
|
+
* expiryTime: number | null,
|
|
56
|
+
* listen: string,
|
|
57
|
+
* port: number,
|
|
58
|
+
* protocol: string,
|
|
59
|
+
* settings: any,
|
|
60
|
+
* streamSettings: any,
|
|
61
|
+
* sniffing: any
|
|
62
|
+
* }>>}
|
|
63
|
+
*/
|
|
64
|
+
async getInbounds() {
|
|
65
|
+
const response = await this.client.get('/panel/api/inbounds/list');
|
|
66
|
+
let inbounds = response.data.obj;
|
|
67
|
+
if (this.parseJSONSettings) {
|
|
68
|
+
inbounds = inbounds.map(inbound => this.parseInbound(inbound));
|
|
69
|
+
}
|
|
70
|
+
return inbounds;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
parseInbound(inbound) {
|
|
74
|
+
if (this.parseJSONSettings) {
|
|
75
|
+
inbound.settings = JSON.parse(inbound.settings);
|
|
76
|
+
inbound.streamSettings = JSON.parse(inbound.streamSettings);
|
|
77
|
+
inbound.sniffing = JSON.parse(inbound.sniffing);
|
|
78
|
+
inbound.allocate = JSON.parse(inbound.allocate);
|
|
79
|
+
}
|
|
80
|
+
return inbound;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
stringifyInbound(inbound) {
|
|
84
|
+
inbound.settings = JSON.stringify(inbound.settings);
|
|
85
|
+
inbound.streamSettings = JSON.stringify(inbound.streamSettings);
|
|
86
|
+
inbound.sniffing = JSON.stringify(inbound.sniffing);
|
|
87
|
+
inbound.allocate = JSON.stringify(inbound.allocate);
|
|
88
|
+
return inbound;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Add new inbound
|
|
93
|
+
* @param {Object} config Inbound configuration
|
|
94
|
+
* @param {number} config.up Upload traffic in bytes
|
|
95
|
+
* @param {number} config.down Download traffic in bytes
|
|
96
|
+
* @param {number} config.total Total traffic limit in bytes
|
|
97
|
+
* @param {string} config.remark Inbound remark/name
|
|
98
|
+
* @param {boolean} config.enable Enable status
|
|
99
|
+
* @param {number|null} config.expiryTime Expiry timestamp
|
|
100
|
+
* @param {string} config.listen Listen address
|
|
101
|
+
* @param {number} config.port Port number
|
|
102
|
+
* @param {string} config.protocol Protocol type
|
|
103
|
+
* @param {Object} config.settings Protocol settings
|
|
104
|
+
* @param {Object} config.streamSettings Stream settings
|
|
105
|
+
* @param {Object} config.sniffing Sniffing settings
|
|
106
|
+
* @param {Object} config.allocate Allocation settings
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
*/
|
|
109
|
+
async addInbound(config) {
|
|
110
|
+
config = this.stringifyInbound(config);
|
|
111
|
+
const response = await this.client.post('/panel/api/inbounds/add', config);
|
|
112
|
+
if(!response.data.success)
|
|
113
|
+
throw new Error(response.data.msg);
|
|
114
|
+
return this.parseInbound(response.data.obj);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Update existing inbound
|
|
119
|
+
* @param {number} id Inbound ID
|
|
120
|
+
* @param {Object} config Partial inbound configuration
|
|
121
|
+
* @returns {Promise<void>}
|
|
122
|
+
*/
|
|
123
|
+
async updateInbound(id, config) {
|
|
124
|
+
config = this.stringifyInbound(config);
|
|
125
|
+
const response = await this.client.post(`/panel/api/inbounds/update/${id}`, config);
|
|
126
|
+
if(!response.data.success)
|
|
127
|
+
throw new Error(response.data.msg);
|
|
128
|
+
return this.parseInbound(response.data.obj);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Delete inbound
|
|
133
|
+
* @param {number} id Inbound ID
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*/
|
|
136
|
+
async deleteInbound(id) {
|
|
137
|
+
await this.client.post(`/panel/api/inbounds/del/${id}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get new X25519 certificate
|
|
142
|
+
* @returns {Promise<{privateKey: string, publicKey: string}>} New X25519 key pair
|
|
143
|
+
*/
|
|
144
|
+
async getNewX25519Cert() {
|
|
145
|
+
const response = await this.client.post('/server/getNewX25519Cert');
|
|
146
|
+
return response.data.obj;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a new Reality inbound builder
|
|
151
|
+
* @param {Partial<import('./index').RealityInboundOptions>} options Initial options
|
|
152
|
+
* @returns {import('./index').RealityBuilder} Reality builder instance
|
|
153
|
+
*/
|
|
154
|
+
createRealityBuilder(options = {}) {
|
|
155
|
+
return new RealityBuilder(this, options);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class ClientBuilder {
|
|
160
|
+
constructor(parent) {
|
|
161
|
+
this.parent = parent;
|
|
162
|
+
this.id = '';
|
|
163
|
+
this.flow = '';
|
|
164
|
+
this.email = '';
|
|
165
|
+
this.limitIp = 0;
|
|
166
|
+
this.totalGB = 0;
|
|
167
|
+
this.expiryTime = 0;
|
|
168
|
+
this.enable = true;
|
|
169
|
+
this.tgId = '';
|
|
170
|
+
this.subId = '';
|
|
171
|
+
this.reset = 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setId(id) {
|
|
175
|
+
this.id = id;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setEmail(email) {
|
|
180
|
+
this.email = email;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
setTotalGB(gb) {
|
|
185
|
+
this.totalGB = gb;
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
setExpiryTime(timestamp) {
|
|
190
|
+
this.expiryTime = timestamp;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
setTgId(id) {
|
|
195
|
+
this.tgId = id;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async build() {
|
|
200
|
+
return {
|
|
201
|
+
id: this.id || crypto.randomUUID(),
|
|
202
|
+
flow: this.flow,
|
|
203
|
+
email: this.email || Math.random().toString(36).substring(2, 10),
|
|
204
|
+
limitIp: this.limitIp,
|
|
205
|
+
totalGB: this.totalGB,
|
|
206
|
+
expiryTime: this.expiryTime,
|
|
207
|
+
enable: this.enable,
|
|
208
|
+
tgId: this.tgId,
|
|
209
|
+
subId: this.subId || Math.random().toString(36).substring(2, 18),
|
|
210
|
+
reset: this.reset
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class RealityBuilder {
|
|
216
|
+
constructor(client, options = {}) {
|
|
217
|
+
this.client = client;
|
|
218
|
+
// Initialize from InboundConfig
|
|
219
|
+
this.id = options.id || undefined;
|
|
220
|
+
this.port = options.port || 0;
|
|
221
|
+
this.remark = options.remark || '';
|
|
222
|
+
this.listenIP = options.listen || '';
|
|
223
|
+
this.expiryTime = options.expiryTime || 0;
|
|
224
|
+
this.enable = true;
|
|
225
|
+
|
|
226
|
+
// Initialize from StreamSettings and RealitySettings
|
|
227
|
+
const streamSettings = typeof options.streamSettings === 'string'
|
|
228
|
+
? JSON.parse(options.streamSettings)
|
|
229
|
+
: options.streamSettings || {};
|
|
230
|
+
|
|
231
|
+
const realitySettings = streamSettings?.realitySettings || {};
|
|
232
|
+
|
|
233
|
+
this.dest = realitySettings.dest || 'yahoo.com:443';
|
|
234
|
+
this.serverNames = realitySettings.serverNames || ['yahoo.com', 'www.yahoo.com'];
|
|
235
|
+
this.privateKey = realitySettings.privateKey || '';
|
|
236
|
+
this.publicKey = realitySettings.settings?.publicKey || '';
|
|
237
|
+
this.shortIds = realitySettings.shortIds;
|
|
238
|
+
this.fingerprint = realitySettings.settings?.fingerprint || 'chrome';
|
|
239
|
+
|
|
240
|
+
// Initialize clients
|
|
241
|
+
this.clients = [];
|
|
242
|
+
const settings = typeof options.settings === 'string'
|
|
243
|
+
? JSON.parse(options.settings)
|
|
244
|
+
: options.settings;
|
|
245
|
+
|
|
246
|
+
if (settings?.clients) {
|
|
247
|
+
settings.clients.forEach(client => {
|
|
248
|
+
this.addClient(client);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
setPort(port) {
|
|
254
|
+
this.port = port;
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setRemark(remark) {
|
|
259
|
+
this.remark = remark;
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
setDest(dest) {
|
|
264
|
+
this.dest = dest;
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setServerNames(names) {
|
|
269
|
+
this.serverNames = names;
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
setKeyPair(privateKey, publicKey) {
|
|
274
|
+
this.privateKey = privateKey;
|
|
275
|
+
this.publicKey = publicKey;
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setShortIds(ids) {
|
|
280
|
+
this.shortIds = ids;
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
setFingerprint(fingerprint) {
|
|
285
|
+
this.fingerprint = fingerprint;
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
setListenIP(ip) {
|
|
290
|
+
this.listenIP = ip;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
setExpiryTime(timestamp) {
|
|
295
|
+
this.expiryTime = timestamp;
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
addClient(options = {}) {
|
|
300
|
+
const builder = new ClientBuilder(this);
|
|
301
|
+
if (options.id) builder.setId(options.id);
|
|
302
|
+
if (options.email) builder.setEmail(options.email);
|
|
303
|
+
if (options.totalGB) builder.setTotalGB(options.totalGB);
|
|
304
|
+
if (options.expiryTime) builder.setExpiryTime(options.expiryTime);
|
|
305
|
+
if (options.tgId) builder.setTgId(options.tgId);
|
|
306
|
+
this.clients.push(builder);
|
|
307
|
+
return builder;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
generateRandomPort() {
|
|
311
|
+
return Math.floor(Math.random() * (65535 - 1024) + 1024);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
generateShortId() {
|
|
315
|
+
const length = Math.floor(Math.random() * 7) * 2 + 2; // Random length between 2 and 16
|
|
316
|
+
return crypto.randomBytes(Math.ceil(length / 2))
|
|
317
|
+
.toString('hex')
|
|
318
|
+
.slice(0, length);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async build() {
|
|
322
|
+
if (!this.remark) {
|
|
323
|
+
throw new Error('Remark is required');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// If no port specified, find unused one
|
|
327
|
+
if (!this.port) {
|
|
328
|
+
const inbounds = await this.client.getInbounds();
|
|
329
|
+
const usedPorts = new Set(inbounds.map(i => i.port));
|
|
330
|
+
let port;
|
|
331
|
+
do {
|
|
332
|
+
port = this.generateRandomPort();
|
|
333
|
+
} while (usedPorts.has(port));
|
|
334
|
+
this.port = port;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// If no keypair provided, generate one
|
|
338
|
+
if (!this.privateKey || !this.publicKey) {
|
|
339
|
+
const cert = await this.client.getNewX25519Cert();
|
|
340
|
+
this.privateKey = cert.privateKey;
|
|
341
|
+
this.publicKey = cert.publicKey;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// If no shortIds provided, generate random ones
|
|
345
|
+
if (!this.shortIds) {
|
|
346
|
+
this.shortIds = Array.from({ length: 8 }, () => this.generateShortId());
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// If no clients added, create one default client
|
|
350
|
+
if (this.clients.length === 0) {
|
|
351
|
+
this.addClient();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Build all clients
|
|
355
|
+
const clientConfigs = await Promise.all(this.clients.map(builder => builder.build()));
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
id: this.id,
|
|
359
|
+
up: 0,
|
|
360
|
+
down: 0,
|
|
361
|
+
total: 0,
|
|
362
|
+
remark: this.remark,
|
|
363
|
+
enable: true,
|
|
364
|
+
expiryTime: this.expiryTime,
|
|
365
|
+
listen: this.listenIP,
|
|
366
|
+
port: this.port,
|
|
367
|
+
protocol: 'vless',
|
|
368
|
+
settings: {
|
|
369
|
+
clients: clientConfigs,
|
|
370
|
+
decryption: 'none',
|
|
371
|
+
fallbacks: []
|
|
372
|
+
},
|
|
373
|
+
streamSettings: {
|
|
374
|
+
network: 'tcp',
|
|
375
|
+
security: 'reality',
|
|
376
|
+
externalProxy: [],
|
|
377
|
+
realitySettings: {
|
|
378
|
+
show: false,
|
|
379
|
+
xver: 0,
|
|
380
|
+
dest: this.dest,
|
|
381
|
+
serverNames: this.serverNames,
|
|
382
|
+
privateKey: this.privateKey,
|
|
383
|
+
minClient: '',
|
|
384
|
+
maxClient: '',
|
|
385
|
+
maxTimediff: 0,
|
|
386
|
+
shortIds: this.shortIds,
|
|
387
|
+
settings: {
|
|
388
|
+
publicKey: this.publicKey,
|
|
389
|
+
fingerprint: this.fingerprint,
|
|
390
|
+
serverName: '',
|
|
391
|
+
spiderX: '/'
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
tcpSettings: {
|
|
395
|
+
acceptProxyProtocol: false,
|
|
396
|
+
header: {
|
|
397
|
+
type: 'none'
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
tag: `inbound-${this.port}`,
|
|
402
|
+
sniffing: {
|
|
403
|
+
enabled: false,
|
|
404
|
+
destOverride: ['http', 'tls', 'quic', 'fakedns'],
|
|
405
|
+
metadataOnly: false,
|
|
406
|
+
routeOnly: false
|
|
407
|
+
},
|
|
408
|
+
allocate: {
|
|
409
|
+
strategy: 'always',
|
|
410
|
+
refresh: 5,
|
|
411
|
+
concurrency: 3
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|