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.
Files changed (60) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +455 -0
  3. package/dist/index.d.mts +1335 -0
  4. package/dist/index.d.ts +1335 -0
  5. package/dist/index.js +4694 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +4604 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +82 -0
  10. package/src/audio/AudioFilters.ts +316 -0
  11. package/src/audio/AudioQueue.ts +782 -0
  12. package/src/audio/AudioTrack.ts +242 -0
  13. package/src/audio/QueueController.ts +252 -0
  14. package/src/audio/TrackCollection.ts +138 -0
  15. package/src/audio/index.ts +9 -0
  16. package/src/config/defaults.ts +223 -0
  17. package/src/config/endpoints.ts +99 -0
  18. package/src/config/index.ts +9 -0
  19. package/src/config/patterns.ts +55 -0
  20. package/src/config/presets.ts +400 -0
  21. package/src/config/symbols.ts +31 -0
  22. package/src/core/PluginSystem.ts +50 -0
  23. package/src/core/RyanlinkPlayer.ts +403 -0
  24. package/src/core/index.ts +6 -0
  25. package/src/extensions/AutoplayExtension.ts +283 -0
  26. package/src/extensions/FairPlayExtension.ts +154 -0
  27. package/src/extensions/LyricsExtension.ts +187 -0
  28. package/src/extensions/PersistenceExtension.ts +182 -0
  29. package/src/extensions/SponsorBlockExtension.ts +81 -0
  30. package/src/extensions/index.ts +9 -0
  31. package/src/index.ts +19 -0
  32. package/src/lavalink/ConnectionPool.ts +326 -0
  33. package/src/lavalink/HttpClient.ts +316 -0
  34. package/src/lavalink/LavalinkConnection.ts +409 -0
  35. package/src/lavalink/index.ts +7 -0
  36. package/src/metadata.ts +88 -0
  37. package/src/types/api/Rest.ts +949 -0
  38. package/src/types/api/Websocket.ts +463 -0
  39. package/src/types/api/index.ts +6 -0
  40. package/src/types/audio/FilterManager.ts +29 -0
  41. package/src/types/audio/Queue.ts +4 -0
  42. package/src/types/audio/QueueManager.ts +30 -0
  43. package/src/types/audio/index.ts +7 -0
  44. package/src/types/common.ts +63 -0
  45. package/src/types/core/Player.ts +322 -0
  46. package/src/types/core/index.ts +5 -0
  47. package/src/types/index.ts +6 -0
  48. package/src/types/lavalink/Node.ts +173 -0
  49. package/src/types/lavalink/NodeManager.ts +34 -0
  50. package/src/types/lavalink/REST.ts +144 -0
  51. package/src/types/lavalink/index.ts +32 -0
  52. package/src/types/voice/VoiceManager.ts +176 -0
  53. package/src/types/voice/index.ts +5 -0
  54. package/src/utils/helpers.ts +169 -0
  55. package/src/utils/index.ts +6 -0
  56. package/src/utils/validators.ts +184 -0
  57. package/src/voice/RegionSelector.ts +184 -0
  58. package/src/voice/VoiceConnection.ts +451 -0
  59. package/src/voice/VoiceSession.ts +297 -0
  60. 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
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Voice exports - VoiceSession, VoiceConnection, RegionSelector
3
+ */
4
+
5
+ export * from "./VoiceSession";
6
+ export * from "./VoiceConnection";
7
+ export * from "./RegionSelector";