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,297 @@
|
|
|
1
|
+
import { LookupSymbol } from "../config/symbols";
|
|
2
|
+
import { noop } from "../utils";
|
|
3
|
+
import type { APIPlayer, BotVoiceState, PlayerUpdateRequestBody } from "../types";
|
|
4
|
+
import type { Node } from "../lavalink/LavalinkConnection";
|
|
5
|
+
import type { Player } from "../core/RyanlinkPlayer";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents the voice connection state for a guild
|
|
9
|
+
* Manages voice connection, node changes, and player state
|
|
10
|
+
*/
|
|
11
|
+
export class VoiceState {
|
|
12
|
+
#changePromise: Promise<void> | null = null;
|
|
13
|
+
#node: Node;
|
|
14
|
+
|
|
15
|
+
#state: BotVoiceState;
|
|
16
|
+
#player: APIPlayer;
|
|
17
|
+
|
|
18
|
+
readonly guildId: string;
|
|
19
|
+
readonly player: Player;
|
|
20
|
+
|
|
21
|
+
constructor(player: Player, node: string, guildId: string) {
|
|
22
|
+
if (player.voices.has(guildId)) {
|
|
23
|
+
throw new Error("An identical voice state already exists");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const _node = player.nodes.get(node);
|
|
27
|
+
if (!_node) {
|
|
28
|
+
throw new Error(`Node '${node}' not found`);
|
|
29
|
+
}
|
|
30
|
+
if (!_node.ready) {
|
|
31
|
+
throw new Error(`Node '${node}' not ready`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const state = player.voices[LookupSymbol](guildId);
|
|
35
|
+
if (!state) {
|
|
36
|
+
throw new Error(`No connection found for guild '${guildId}'`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const _player = player.queues[LookupSymbol](guildId);
|
|
40
|
+
if (!_player) {
|
|
41
|
+
throw new Error(`No player found for guild '${guildId}'`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.#node = _node;
|
|
45
|
+
this.#state = state;
|
|
46
|
+
this.#player = _player;
|
|
47
|
+
|
|
48
|
+
this.guildId = guildId;
|
|
49
|
+
this.player = player;
|
|
50
|
+
|
|
51
|
+
// Make properties immutable
|
|
52
|
+
const immutable: PropertyDescriptor = {
|
|
53
|
+
writable: false,
|
|
54
|
+
configurable: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Object.defineProperties(this, {
|
|
58
|
+
guildId: immutable,
|
|
59
|
+
player: { ...immutable, enumerable: false },
|
|
60
|
+
} as PropertyDescriptorMap);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Current node for this voice connection
|
|
65
|
+
*/
|
|
66
|
+
get node(): Node {
|
|
67
|
+
return this.#node;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Ping to the voice server in milliseconds
|
|
72
|
+
*/
|
|
73
|
+
get ping(): number {
|
|
74
|
+
return this.#player.state.ping;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Voice region ID (e.g., "us-west", "eu-central")
|
|
79
|
+
*/
|
|
80
|
+
get regionId(): string | null {
|
|
81
|
+
return this.#state.region_id;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Voice channel ID
|
|
86
|
+
*/
|
|
87
|
+
get channelId(): string {
|
|
88
|
+
return this.#state.channel_id;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether the bot is self-deafened
|
|
93
|
+
*/
|
|
94
|
+
get selfDeaf(): boolean {
|
|
95
|
+
return this.#state.self_deaf;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether the bot is self-muted
|
|
100
|
+
*/
|
|
101
|
+
get selfMute(): boolean {
|
|
102
|
+
return this.#state.self_mute;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether the bot is server-deafened
|
|
107
|
+
*/
|
|
108
|
+
get serverDeaf(): boolean {
|
|
109
|
+
return this.#state.deaf;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether the bot is server-muted
|
|
114
|
+
*/
|
|
115
|
+
get serverMute(): boolean {
|
|
116
|
+
return this.#state.mute;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether the bot is suppressed (priority speaker)
|
|
121
|
+
*/
|
|
122
|
+
get suppressed(): boolean {
|
|
123
|
+
return this.#state.suppress;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether this voice state has been destroyed
|
|
128
|
+
*/
|
|
129
|
+
get destroyed(): boolean {
|
|
130
|
+
return this.player.voices.get(this.guildId) !== this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the voice connection is fully connected
|
|
135
|
+
*/
|
|
136
|
+
get connected(): boolean {
|
|
137
|
+
if (!this.#player.state.connected) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return this.#state.connected && this.#state.node_session_id === this.#node.sessionId;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Whether the voice connection is reconnecting
|
|
145
|
+
*/
|
|
146
|
+
get reconnecting(): boolean {
|
|
147
|
+
return this.#state.reconnecting;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Whether the voice connection is disconnected
|
|
152
|
+
*/
|
|
153
|
+
get disconnected(): boolean {
|
|
154
|
+
return !this.connected && !this.reconnecting;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Whether the voice connection is changing nodes
|
|
159
|
+
*/
|
|
160
|
+
get changingNode(): boolean {
|
|
161
|
+
return this.#changePromise !== null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Session ID for the voice connection
|
|
166
|
+
*/
|
|
167
|
+
get sessionId(): string | null {
|
|
168
|
+
return this.#state.session_id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Voice server token
|
|
173
|
+
*/
|
|
174
|
+
get token(): string | null {
|
|
175
|
+
return this.#state.token;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Voice server endpoint
|
|
180
|
+
*/
|
|
181
|
+
get endpoint(): string | null {
|
|
182
|
+
return this.#state.endpoint;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Node session ID this voice state is connected to
|
|
187
|
+
*/
|
|
188
|
+
get nodeSessionId(): string | null {
|
|
189
|
+
return this.#state.node_session_id;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Destroy this voice connection
|
|
194
|
+
* @param reason Optional reason for destruction
|
|
195
|
+
*/
|
|
196
|
+
async destroy(reason?: string): Promise<void> {
|
|
197
|
+
return this.player.voices.destroy(this.guildId, reason);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Connect to a voice channel
|
|
202
|
+
* @param channelId Voice channel ID (defaults to current channel)
|
|
203
|
+
*/
|
|
204
|
+
async connect(channelId = this.#state.channel_id): Promise<void> {
|
|
205
|
+
await this.player.voices.connect(this.guildId, channelId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Disconnect from the voice channel
|
|
210
|
+
*/
|
|
211
|
+
async disconnect(): Promise<void> {
|
|
212
|
+
return this.player.voices.disconnect(this.guildId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Change to a different Lavalink node
|
|
217
|
+
* Preserves playback state and filters
|
|
218
|
+
* @param name Name of the node to change to
|
|
219
|
+
*/
|
|
220
|
+
async changeNode(name: string): Promise<void> {
|
|
221
|
+
const node = this.player.nodes.get(name);
|
|
222
|
+
|
|
223
|
+
if (!node) {
|
|
224
|
+
throw new Error(`Node '${name}' not found`);
|
|
225
|
+
}
|
|
226
|
+
if (!node.ready) {
|
|
227
|
+
throw new Error(`Node '${name}' not ready`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (this.#changePromise !== null) {
|
|
231
|
+
return this.#changePromise;
|
|
232
|
+
}
|
|
233
|
+
if (name === this.#node.name) {
|
|
234
|
+
throw new Error(`Already on node '${name}'`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let resolve!: () => void;
|
|
238
|
+
let reject!: (reason?: unknown) => void;
|
|
239
|
+
const promise = new Promise<void>((res, rej) => {
|
|
240
|
+
resolve = res;
|
|
241
|
+
reject = rej;
|
|
242
|
+
});
|
|
243
|
+
const resolver = { promise, resolve, reject };
|
|
244
|
+
this.#changePromise = resolver.promise;
|
|
245
|
+
|
|
246
|
+
// Prepare player update request
|
|
247
|
+
const request: PlayerUpdateRequestBody = {
|
|
248
|
+
voice: {
|
|
249
|
+
channelId: this.#state.channel_id,
|
|
250
|
+
endpoint: this.#state.endpoint,
|
|
251
|
+
sessionId: this.#state.session_id,
|
|
252
|
+
token: this.#state.token,
|
|
253
|
+
},
|
|
254
|
+
filters: this.#player.filters,
|
|
255
|
+
paused: this.#player.paused,
|
|
256
|
+
volume: this.#player.volume,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const track = this.#player.track;
|
|
260
|
+
const wasPlaying = !this.#player.paused && track !== null;
|
|
261
|
+
|
|
262
|
+
// Only transfer track if the new node supports the source
|
|
263
|
+
if (wasPlaying && this.player.nodes.supports("source", track.info.sourceName, node.name)) {
|
|
264
|
+
request.track = { encoded: track.encoded, userData: track.userData };
|
|
265
|
+
request.position = this.#player.state.position;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Destroy player on old node
|
|
269
|
+
await this.#node.rest.destroyPlayer(this.guildId).catch(noop);
|
|
270
|
+
|
|
271
|
+
const previousNode = this.#node;
|
|
272
|
+
this.#node = node;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
// Create player on new node
|
|
276
|
+
const player = await node.rest.updatePlayer(this.guildId, request);
|
|
277
|
+
this.#state.node_session_id = node.sessionId ?? "";
|
|
278
|
+
Object.assign(this.#player, player);
|
|
279
|
+
|
|
280
|
+
// Emit voice change event
|
|
281
|
+
this.player.emit("voiceChange", this, previousNode, wasPlaying);
|
|
282
|
+
resolver.resolve();
|
|
283
|
+
} catch (err) {
|
|
284
|
+
resolver.reject(err);
|
|
285
|
+
throw err;
|
|
286
|
+
} finally {
|
|
287
|
+
this.#changePromise = null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* String representation of the voice state
|
|
293
|
+
*/
|
|
294
|
+
toString(): string {
|
|
295
|
+
return `VoiceState<${this.guildId}>`;
|
|
296
|
+
}
|
|
297
|
+
}
|