ryanlink 1.0.1
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/LICENSE +37 -0
- package/README.md +455 -0
- package/dist/index.d.mts +1335 -0
- package/dist/index.d.ts +1335 -0
- package/dist/index.js +4694 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4604 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +82 -0
- package/src/audio/AudioFilters.ts +316 -0
- package/src/audio/AudioQueue.ts +782 -0
- package/src/audio/AudioTrack.ts +242 -0
- package/src/audio/QueueController.ts +252 -0
- package/src/audio/TrackCollection.ts +138 -0
- package/src/audio/index.ts +9 -0
- package/src/config/defaults.ts +223 -0
- package/src/config/endpoints.ts +99 -0
- package/src/config/index.ts +9 -0
- package/src/config/patterns.ts +55 -0
- package/src/config/presets.ts +400 -0
- package/src/config/symbols.ts +31 -0
- package/src/core/PluginSystem.ts +50 -0
- package/src/core/RyanlinkPlayer.ts +403 -0
- package/src/core/index.ts +6 -0
- package/src/extensions/AutoplayExtension.ts +283 -0
- package/src/extensions/FairPlayExtension.ts +154 -0
- package/src/extensions/LyricsExtension.ts +187 -0
- package/src/extensions/PersistenceExtension.ts +182 -0
- package/src/extensions/SponsorBlockExtension.ts +81 -0
- package/src/extensions/index.ts +9 -0
- package/src/index.ts +19 -0
- package/src/lavalink/ConnectionPool.ts +326 -0
- package/src/lavalink/HttpClient.ts +316 -0
- package/src/lavalink/LavalinkConnection.ts +409 -0
- package/src/lavalink/index.ts +7 -0
- package/src/metadata.ts +88 -0
- package/src/types/api/Rest.ts +949 -0
- package/src/types/api/Websocket.ts +463 -0
- package/src/types/api/index.ts +6 -0
- package/src/types/audio/FilterManager.ts +29 -0
- package/src/types/audio/Queue.ts +4 -0
- package/src/types/audio/QueueManager.ts +30 -0
- package/src/types/audio/index.ts +7 -0
- package/src/types/common.ts +63 -0
- package/src/types/core/Player.ts +322 -0
- package/src/types/core/index.ts +5 -0
- package/src/types/index.ts +6 -0
- package/src/types/lavalink/Node.ts +173 -0
- package/src/types/lavalink/NodeManager.ts +34 -0
- package/src/types/lavalink/REST.ts +144 -0
- package/src/types/lavalink/index.ts +32 -0
- package/src/types/voice/VoiceManager.ts +176 -0
- package/src/types/voice/index.ts +5 -0
- package/src/utils/helpers.ts +169 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/validators.ts +184 -0
- package/src/voice/RegionSelector.ts +184 -0
- package/src/voice/VoiceConnection.ts +451 -0
- package/src/voice/VoiceSession.ts +297 -0
- package/src/voice/index.ts +7 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { EventEmitter, once } from "node:events";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { clearTimeout, setTimeout } from "node:timers";
|
|
4
|
+
import { WebSocket, type ClientOptions } from "ws";
|
|
5
|
+
import { REST } from "./HttpClient";
|
|
6
|
+
import { CLIENT_NAME, CLIENT_VERSION } from "../metadata";
|
|
7
|
+
import { OPType } from "../types/api/Websocket";
|
|
8
|
+
import type {
|
|
9
|
+
NodeOptions,
|
|
10
|
+
NodeState,
|
|
11
|
+
NodeEventMap,
|
|
12
|
+
MessagePayload,
|
|
13
|
+
StatsPayload,
|
|
14
|
+
ClientHeaders,
|
|
15
|
+
} from "../types/lavalink";
|
|
16
|
+
|
|
17
|
+
export enum CloseCodes {
|
|
18
|
+
Normal = 1000,
|
|
19
|
+
GoingAway = 1001,
|
|
20
|
+
ProtocolError = 1002,
|
|
21
|
+
UnsupportedData = 1003,
|
|
22
|
+
NoStatusReceived = 1005,
|
|
23
|
+
AbnormalClosure = 1006,
|
|
24
|
+
InvalidFramePayloadData = 1007,
|
|
25
|
+
PolicyViolation = 1008,
|
|
26
|
+
MessageTooBig = 1009,
|
|
27
|
+
InternalError = 1011,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Represents a Lavalink node connection
|
|
32
|
+
* Handles WebSocket connection, reconnection, and session management
|
|
33
|
+
*/
|
|
34
|
+
export class LavalinkNode extends EventEmitter<NodeEventMap> {
|
|
35
|
+
#socketConfig: ClientOptions & { headers: ClientHeaders };
|
|
36
|
+
#connectPromise: Promise<boolean> | null = null;
|
|
37
|
+
#disconnectPromise: Promise<void> | null = null;
|
|
38
|
+
|
|
39
|
+
#pingTimer: NodeJS.Timeout | null = null;
|
|
40
|
+
#reconnectTimer: NodeJS.Timeout | null = null;
|
|
41
|
+
|
|
42
|
+
#ping: number | null = null;
|
|
43
|
+
#lastPingTime: number | null = null;
|
|
44
|
+
|
|
45
|
+
#reconnectCycle = true;
|
|
46
|
+
#reconnectAttempts = 0;
|
|
47
|
+
#manualDisconnect = false;
|
|
48
|
+
|
|
49
|
+
#socket: WebSocket | null = null;
|
|
50
|
+
#stats: StatsPayload | null = null;
|
|
51
|
+
|
|
52
|
+
#socketUrl: string;
|
|
53
|
+
#pingTimeout: number;
|
|
54
|
+
#reconnectDelay: number;
|
|
55
|
+
#reconnectLimit: number;
|
|
56
|
+
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly rest: REST;
|
|
59
|
+
|
|
60
|
+
constructor(options: NodeOptions) {
|
|
61
|
+
super({ captureRejections: false });
|
|
62
|
+
|
|
63
|
+
// Validate options
|
|
64
|
+
if (!options.name || typeof options.name !== "string") {
|
|
65
|
+
throw new Error("Node name must be a non-empty string");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!options.clientId || typeof options.clientId !== "string") {
|
|
69
|
+
throw new Error("Client ID must be a non-empty string");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Initialize REST client
|
|
73
|
+
this.rest = new REST(options);
|
|
74
|
+
|
|
75
|
+
// Setup socket configuration
|
|
76
|
+
this.#socketConfig = {
|
|
77
|
+
headers: {
|
|
78
|
+
"Client-Name": `${CLIENT_NAME}/${CLIENT_VERSION}`,
|
|
79
|
+
"User-Id": options.clientId,
|
|
80
|
+
"User-Agent": this.rest.userAgent,
|
|
81
|
+
Authorization: options.password,
|
|
82
|
+
},
|
|
83
|
+
perMessageDeflate: false,
|
|
84
|
+
handshakeTimeout: options.handshakeTimeout ?? 30000,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Restore session if provided
|
|
88
|
+
if (this.rest.sessionId) {
|
|
89
|
+
this.#socketConfig.headers["Session-Id"] = this.rest.sessionId;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Build WebSocket URL
|
|
93
|
+
const protocol = options.secure ? "wss" : "ws";
|
|
94
|
+
this.#socketUrl = `${protocol}://${options.host}:${options.port}/v4/websocket`;
|
|
95
|
+
|
|
96
|
+
// Configure timeouts and reconnection
|
|
97
|
+
this.#pingTimeout = (options.statsInterval ?? 60000) + (options.highestLatency ?? 5000);
|
|
98
|
+
this.#reconnectDelay = options.reconnectDelay ?? 5000;
|
|
99
|
+
this.#reconnectLimit = options.reconnectLimit ?? -1; // -1 = infinite
|
|
100
|
+
|
|
101
|
+
this.name = options.name;
|
|
102
|
+
|
|
103
|
+
// Make REST sessionId readonly but linked to socket
|
|
104
|
+
Object.defineProperty(this.rest, "sessionId", {
|
|
105
|
+
configurable: false,
|
|
106
|
+
get: () => this.sessionId,
|
|
107
|
+
set: () => {},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Make properties immutable
|
|
111
|
+
const immutable: PropertyDescriptor = {
|
|
112
|
+
writable: false,
|
|
113
|
+
configurable: false,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
Object.defineProperties(this, {
|
|
117
|
+
name: immutable,
|
|
118
|
+
rest: immutable,
|
|
119
|
+
} as PropertyDescriptorMap);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get clientId(): string {
|
|
123
|
+
return this.#socketConfig.headers["User-Id"];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get sessionId(): string | null {
|
|
127
|
+
return this.#socketConfig.headers["Session-Id"] ?? null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get ping(): number | null {
|
|
131
|
+
return this.#ping;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get stats(): StatsPayload | null {
|
|
135
|
+
return this.#stats;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get state(): NodeState {
|
|
139
|
+
if (this.connecting) {
|
|
140
|
+
return "connecting";
|
|
141
|
+
}
|
|
142
|
+
if (this.connected) {
|
|
143
|
+
return this.ready ? "ready" : "connected";
|
|
144
|
+
}
|
|
145
|
+
return this.reconnecting ? "reconnecting" : "disconnected";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get connecting(): boolean {
|
|
149
|
+
return this.#socket?.readyState === WebSocket.CONNECTING;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get connected(): boolean {
|
|
153
|
+
return this.#socket?.readyState === WebSocket.OPEN;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get ready(): boolean {
|
|
157
|
+
return this.connected && this.sessionId !== null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get reconnecting(): boolean {
|
|
161
|
+
return this.#socket === null && this.#reconnectTimer !== null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get disconnected(): boolean {
|
|
165
|
+
return this.#socket === null && !this.reconnecting;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get reconnectLimit(): number {
|
|
169
|
+
return this.#reconnectLimit;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get reconnectAttempts(): number {
|
|
173
|
+
return this.#reconnectAttempts;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#error(err: Error | (Error & { errors?: Error[] })): Error {
|
|
177
|
+
const data = "errors" in err && Array.isArray(err.errors) ? err.errors[err.errors.length - 1] : err;
|
|
178
|
+
const error = data instanceof Error ? data : new Error(String(data));
|
|
179
|
+
error.name = `Error [${(this.constructor as { name: string }).name}]`;
|
|
180
|
+
return error;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#cleanup(): void {
|
|
184
|
+
this.#socket?.removeAllListeners();
|
|
185
|
+
if (this.#pingTimer !== null) {
|
|
186
|
+
clearTimeout(this.#pingTimer);
|
|
187
|
+
}
|
|
188
|
+
this.#socket = this.#pingTimer = this.#stats = null;
|
|
189
|
+
this.#lastPingTime = this.#ping = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#reconnect(): void {
|
|
193
|
+
this.#reconnectCycle = false;
|
|
194
|
+
this.#reconnectTimer?.refresh();
|
|
195
|
+
this.#reconnectTimer ??= setTimeout(() => {
|
|
196
|
+
this.#reconnectCycle = true;
|
|
197
|
+
void this.connect();
|
|
198
|
+
}, this.#reconnectDelay).unref();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#stopReconnecting(resetCount = true, reconnectCycle = false): void {
|
|
202
|
+
this.#reconnectCycle = reconnectCycle;
|
|
203
|
+
if (resetCount) {
|
|
204
|
+
this.#reconnectAttempts = 0;
|
|
205
|
+
}
|
|
206
|
+
if (this.#reconnectTimer !== null) {
|
|
207
|
+
clearTimeout(this.#reconnectTimer);
|
|
208
|
+
}
|
|
209
|
+
this.#reconnectTimer = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#keepAliveAndPing(): void {
|
|
213
|
+
this.#pingTimer?.refresh();
|
|
214
|
+
this.#pingTimer ??= setTimeout(() => {
|
|
215
|
+
this.#socket?.terminate();
|
|
216
|
+
this.#cleanup();
|
|
217
|
+
this.#reconnect();
|
|
218
|
+
}, this.#pingTimeout).unref();
|
|
219
|
+
|
|
220
|
+
// Record timestamp before sending ping
|
|
221
|
+
this.#lastPingTime = Date.now();
|
|
222
|
+
this.#socket?.ping();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#parseMessageData(data: string): MessagePayload | null {
|
|
226
|
+
try {
|
|
227
|
+
return JSON.parse(data) as MessagePayload;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Connect to the Lavalink node
|
|
235
|
+
* Handles reconnection attempts and session resumption
|
|
236
|
+
*/
|
|
237
|
+
async connect(): Promise<boolean> {
|
|
238
|
+
if (this.#socket !== null) {
|
|
239
|
+
return this.#connectPromise ?? this.connected;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this.reconnecting) {
|
|
243
|
+
this.#reconnectAttempts++;
|
|
244
|
+
if (!this.#reconnectCycle) {
|
|
245
|
+
this.#stopReconnecting(false, true);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.#socket = new WebSocket(this.#socketUrl, this.#socketConfig);
|
|
250
|
+
|
|
251
|
+
this.#socket.once("open", () => {
|
|
252
|
+
this.emit("connect", this.#reconnectAttempts, this.name);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
this.#socket.on("message", (data) => {
|
|
256
|
+
void this.#onMessage((data as Buffer).toString("utf8"));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.#socket.on("error", (err) => {
|
|
260
|
+
this.emit("error", this.#error(err), this.name);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this.#socket.on("close", (code, reason) => {
|
|
264
|
+
this.#onClose(code, reason.toString("utf8"));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.#socket.on("pong", () => {
|
|
268
|
+
if (this.#lastPingTime === null) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.#ping = Math.max(0, Date.now() - this.#lastPingTime);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
let resolve!: (value: boolean) => void;
|
|
275
|
+
let reject!: (reason?: unknown) => void;
|
|
276
|
+
const promise = new Promise<boolean>((res, rej) => {
|
|
277
|
+
resolve = res;
|
|
278
|
+
reject = rej;
|
|
279
|
+
});
|
|
280
|
+
const resolver = { promise, resolve, reject };
|
|
281
|
+
this.#connectPromise = resolver.promise;
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await Promise.race([
|
|
286
|
+
once(this.#socket, "open", { signal: controller.signal }),
|
|
287
|
+
once(this.#socket, "close", { signal: controller.signal }),
|
|
288
|
+
]);
|
|
289
|
+
} catch {
|
|
290
|
+
this.#cleanup();
|
|
291
|
+
} finally {
|
|
292
|
+
controller.abort();
|
|
293
|
+
const connected = this.connected;
|
|
294
|
+
resolver.resolve(connected);
|
|
295
|
+
this.#connectPromise = null;
|
|
296
|
+
}
|
|
297
|
+
return this.connected;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Disconnect from the Lavalink node
|
|
302
|
+
* @param code - WebSocket close code
|
|
303
|
+
* @param reason - Disconnect reason
|
|
304
|
+
*/
|
|
305
|
+
async disconnect(code: number = CloseCodes.Normal, reason = "disconnected"): Promise<void> {
|
|
306
|
+
if (this.#disconnectPromise !== null) {
|
|
307
|
+
return this.#disconnectPromise;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.#stopReconnecting();
|
|
311
|
+
|
|
312
|
+
if (this.#socket === null) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (this.connecting) {
|
|
317
|
+
this.#manualDisconnect = true;
|
|
318
|
+
this.#socket.terminate();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!this.connected) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.#manualDisconnect = true;
|
|
327
|
+
this.#disconnectPromise = once(this.#socket, "close").then(
|
|
328
|
+
() => {},
|
|
329
|
+
() => {},
|
|
330
|
+
);
|
|
331
|
+
this.#socket.close(code, reason);
|
|
332
|
+
await this.#disconnectPromise;
|
|
333
|
+
this.#disconnectPromise = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async #onMessage(data: string): Promise<void> {
|
|
337
|
+
const payload = this.#parseMessageData(data);
|
|
338
|
+
if (payload === null) {
|
|
339
|
+
return this.disconnect(CloseCodes.UnsupportedData, "expected json payload");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (payload.op === OPType.Stats) {
|
|
343
|
+
this.#stats = payload;
|
|
344
|
+
this.#keepAliveAndPing();
|
|
345
|
+
} else if (payload.op === OPType.Ready) {
|
|
346
|
+
this.#stopReconnecting();
|
|
347
|
+
this.#socketConfig.headers["Session-Id"] = payload.sessionId;
|
|
348
|
+
this.emit("ready", payload.resumed, payload.sessionId, this.name);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.emit("dispatch", payload, this.name);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#onClose(code: number, reason: string): void {
|
|
355
|
+
this.#cleanup();
|
|
356
|
+
|
|
357
|
+
// Check if we should stop reconnecting
|
|
358
|
+
const shouldStop =
|
|
359
|
+
this.#manualDisconnect || (this.#reconnectLimit >= 0 && this.#reconnectAttempts >= this.#reconnectLimit);
|
|
360
|
+
|
|
361
|
+
if (shouldStop) {
|
|
362
|
+
this.#stopReconnecting();
|
|
363
|
+
delete this.#socketConfig.headers["Session-Id"];
|
|
364
|
+
const byLocal = this.#manualDisconnect;
|
|
365
|
+
this.#manualDisconnect = false;
|
|
366
|
+
this.emit("disconnect", code, reason, byLocal, this.name);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (this.#reconnectCycle) {
|
|
371
|
+
this.#reconnect();
|
|
372
|
+
this.emit("close", code, reason, this.name);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Immediate reconnect attempt
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
this.#reconnectCycle = true;
|
|
379
|
+
void this.connect();
|
|
380
|
+
}, 0);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Set SponsorBlock segments for a player
|
|
385
|
+
*/
|
|
386
|
+
async setSponsorBlock(player: { guildId: string }, segments: string[]): Promise<void> {
|
|
387
|
+
return this.rest.setSponsorBlock(player.guildId, segments);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get current SponsorBlock segments for a player
|
|
392
|
+
*/
|
|
393
|
+
async getSponsorBlock(player: { guildId: string }): Promise<string[]> {
|
|
394
|
+
return this.rest.getSponsorBlock(player.guildId);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Delete SponsorBlock configuration for a player
|
|
399
|
+
*/
|
|
400
|
+
async deleteSponsorBlock(player: { guildId: string }): Promise<void> {
|
|
401
|
+
return this.rest.deleteSponsorBlock(player.guildId);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
override toString(): string {
|
|
405
|
+
return this.name;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export { LavalinkNode as Node };
|
package/src/metadata.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
interface PackageJson {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
repository?: {
|
|
9
|
+
type: string;
|
|
10
|
+
url: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let packageInfo: PackageJson | null = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load package.json information
|
|
18
|
+
* Cached after first load for performance
|
|
19
|
+
*/
|
|
20
|
+
function loadPackageInfo(): PackageJson {
|
|
21
|
+
if (packageInfo) {
|
|
22
|
+
return packageInfo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const possiblePaths = [
|
|
27
|
+
join(__dirname, "..", "..", "package.json"),
|
|
28
|
+
join(__dirname, "..", "package.json"),
|
|
29
|
+
join(process.cwd(), "package.json"),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const path of possiblePaths) {
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(path, "utf-8");
|
|
35
|
+
packageInfo = JSON.parse(content) as PackageJson;
|
|
36
|
+
return packageInfo;
|
|
37
|
+
} catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.warn("Could not load package.json, using fallback values");
|
|
43
|
+
return {
|
|
44
|
+
name: "ryanlink",
|
|
45
|
+
version: "1.0.0",
|
|
46
|
+
repository: {
|
|
47
|
+
type: "git",
|
|
48
|
+
url: "https://github.com/ryanwtf7/ryanlink.git",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
name: "ryanlink",
|
|
54
|
+
version: "1.0.0",
|
|
55
|
+
repository: {
|
|
56
|
+
type: "git",
|
|
57
|
+
url: "https://github.com/ryanwtf7/ryanlink.git",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pkg = loadPackageInfo();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Package name
|
|
67
|
+
*/
|
|
68
|
+
export const CLIENT_NAME = pkg.name;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Package version
|
|
72
|
+
*/
|
|
73
|
+
export const CLIENT_VERSION = pkg.version;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Package repository URL
|
|
77
|
+
*/
|
|
78
|
+
export const CLIENT_REPOSITORY = pkg.repository?.url.replace(/\.git$/, "") ?? "https://github.com/ryanwtf7/ryanlink";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Full package information
|
|
82
|
+
*/
|
|
83
|
+
export const PACKAGE_INFO = Object.freeze({
|
|
84
|
+
name: CLIENT_NAME,
|
|
85
|
+
version: CLIENT_VERSION,
|
|
86
|
+
repository: CLIENT_REPOSITORY,
|
|
87
|
+
description: pkg.description,
|
|
88
|
+
});
|