magmastream 2.9.0-dev.9 → 2.9.1-dev.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 +15 -33
- package/dist/index.d.ts +2813 -1502
- package/dist/index.js +14 -1
- package/dist/statestorage/JsonQueue.js +443 -0
- package/dist/{structures/Queue.js → statestorage/MemoryQueue.js} +263 -184
- package/dist/{structures → statestorage}/RedisQueue.js +310 -164
- package/dist/structures/Enums.js +263 -0
- package/dist/structures/Filters.js +159 -137
- package/dist/structures/Manager.js +755 -444
- package/dist/structures/Node.js +361 -189
- package/dist/structures/Player.js +275 -112
- package/dist/structures/Plugin.js +4 -1
- package/dist/structures/Rest.js +12 -8
- package/dist/structures/Types.js +3 -0
- package/dist/structures/Utils.js +453 -414
- package/dist/utils/managerCheck.js +8 -8
- package/dist/utils/nodeCheck.js +5 -5
- package/dist/utils/playerCheck.js +3 -3
- package/dist/wrappers/detritus.js +36 -0
- package/dist/wrappers/discord.js.js +29 -0
- package/dist/wrappers/eris.js +29 -0
- package/dist/wrappers/oceanic.js +29 -0
- package/dist/wrappers/seyfert.js +43 -0
- package/package.json +20 -15
- package/dist/storage/CollectionPlayerStore.js +0 -77
- package/dist/storage/RedisPlayerStore.js +0 -156
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Player = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const Filters_1 = require("./Filters");
|
|
6
|
-
const
|
|
7
|
-
const Node_1 = require("./Node");
|
|
8
|
-
const Queue_1 = require("./Queue");
|
|
6
|
+
const MemoryQueue_1 = require("../statestorage/MemoryQueue");
|
|
9
7
|
const Utils_1 = require("./Utils");
|
|
10
8
|
const _ = tslib_1.__importStar(require("lodash"));
|
|
11
9
|
const playerCheck_1 = tslib_1.__importDefault(require("../utils/playerCheck"));
|
|
12
|
-
const RedisQueue_1 = require("
|
|
10
|
+
const RedisQueue_1 = require("../statestorage/RedisQueue");
|
|
11
|
+
const Enums_1 = require("./Enums");
|
|
12
|
+
const ws_1 = require("ws");
|
|
13
|
+
const JsonQueue_1 = require("../statestorage/JsonQueue");
|
|
13
14
|
class Player {
|
|
14
15
|
options;
|
|
15
16
|
/** The Queue for the Player. */
|
|
@@ -41,7 +42,7 @@ class Player {
|
|
|
41
42
|
/**The now playing message. */
|
|
42
43
|
nowPlayingMessage;
|
|
43
44
|
/** The current state of the player. */
|
|
44
|
-
state =
|
|
45
|
+
state = Enums_1.StateTypes.Disconnected;
|
|
45
46
|
/** The equalizer bands array. */
|
|
46
47
|
bands = new Array(15).fill(0.0);
|
|
47
48
|
/** The voice state object from Discord. */
|
|
@@ -52,10 +53,18 @@ class Player {
|
|
|
52
53
|
isAutoplay = false;
|
|
53
54
|
/** The number of times to try autoplay before emitting queueEnd. */
|
|
54
55
|
autoplayTries = 3;
|
|
55
|
-
|
|
56
|
+
/** The cluster ID for the player. */
|
|
57
|
+
clusterId = 0;
|
|
56
58
|
data = {};
|
|
57
59
|
dynamicLoopInterval = null;
|
|
58
60
|
dynamicRepeatIntervalMs = null;
|
|
61
|
+
static _manager;
|
|
62
|
+
/** Should only be used when the node is a NodeLink */
|
|
63
|
+
voiceReceiverWsClient;
|
|
64
|
+
isConnectToVoiceReceiver;
|
|
65
|
+
voiceReceiverReconnectTimeout;
|
|
66
|
+
voiceReceiverAttempt;
|
|
67
|
+
voiceReceiverReconnectTries;
|
|
59
68
|
/**
|
|
60
69
|
* Creates a new player, returns one if it already exists.
|
|
61
70
|
* @param options The player options.
|
|
@@ -68,6 +77,7 @@ class Player {
|
|
|
68
77
|
this.manager = Utils_1.Structure.get("Player")._manager;
|
|
69
78
|
if (!this.manager)
|
|
70
79
|
throw new RangeError("Manager has not been initiated.");
|
|
80
|
+
this.clusterId = this.manager.options.clusterId || 0;
|
|
71
81
|
// Check the player options for errors.
|
|
72
82
|
(0, playerCheck_1.default)(options);
|
|
73
83
|
// Set the guild ID and voice state.
|
|
@@ -82,29 +92,40 @@ class Player {
|
|
|
82
92
|
if (options.textChannelId)
|
|
83
93
|
this.textChannelId = options.textChannelId;
|
|
84
94
|
// Set the node to use, either the specified node or the first available node.
|
|
85
|
-
const node = this.manager.nodes.get(options.
|
|
95
|
+
const node = this.manager.nodes.get(options.nodeIdentifier);
|
|
86
96
|
this.node = node || this.manager.useableNode;
|
|
87
97
|
// If no node is available, throw an error.
|
|
88
98
|
if (!this.node)
|
|
89
99
|
throw new RangeError("No available nodes.");
|
|
90
100
|
// Initialize the queue with the guild ID and manager.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
switch (this.manager.options.stateStorage.type) {
|
|
102
|
+
case Enums_1.StateStorageType.Redis:
|
|
103
|
+
this.queue = new RedisQueue_1.RedisQueue(this.guildId, this.manager);
|
|
104
|
+
break;
|
|
105
|
+
case Enums_1.StateStorageType.Memory:
|
|
106
|
+
this.queue = new MemoryQueue_1.MemoryQueue(this.guildId, this.manager);
|
|
107
|
+
break;
|
|
108
|
+
case Enums_1.StateStorageType.JSON:
|
|
109
|
+
this.queue = new JsonQueue_1.JsonQueue(this.guildId, this.manager);
|
|
110
|
+
break;
|
|
99
111
|
}
|
|
100
112
|
// Add the player to the manager's player collection.
|
|
101
113
|
this.manager.players.set(options.guildId, this);
|
|
102
114
|
// Set the initial volume.
|
|
103
115
|
this.setVolume(options.volume ?? 100);
|
|
104
116
|
// Initialize the filters.
|
|
105
|
-
this.filters = new Filters_1.Filters(this);
|
|
117
|
+
this.filters = new Filters_1.Filters(this, this.manager);
|
|
106
118
|
// Emit the playerCreate event.
|
|
107
|
-
this.manager.emit(
|
|
119
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerCreate, this);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Initializes the static properties of the Player class.
|
|
123
|
+
* @hidden
|
|
124
|
+
* @param manager The Manager to use.
|
|
125
|
+
*/
|
|
126
|
+
static init(manager) {
|
|
127
|
+
// Set the Manager to use.
|
|
128
|
+
this._manager = manager;
|
|
108
129
|
}
|
|
109
130
|
/**
|
|
110
131
|
* Set custom data.
|
|
@@ -125,15 +146,6 @@ class Player {
|
|
|
125
146
|
// Access the data object using the key and cast it to the specified type T.
|
|
126
147
|
return this.data[key];
|
|
127
148
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Initializes the static properties of the Player class.
|
|
130
|
-
* @hidden
|
|
131
|
-
* @param manager The Manager to use.
|
|
132
|
-
*/
|
|
133
|
-
static init(manager) {
|
|
134
|
-
// Set the Manager to use.
|
|
135
|
-
this._manager = manager;
|
|
136
|
-
}
|
|
137
149
|
/**
|
|
138
150
|
* Same as Manager#search() but a shortcut on the player itself.
|
|
139
151
|
* @param query
|
|
@@ -153,11 +165,10 @@ class Player {
|
|
|
153
165
|
throw new RangeError("No voice channel has been set. You must use the `setVoiceChannelId()` method to set the voice channel before connecting.");
|
|
154
166
|
}
|
|
155
167
|
// Set the player state to connecting.
|
|
156
|
-
this.state =
|
|
168
|
+
this.state = Enums_1.StateTypes.Connecting;
|
|
157
169
|
// Clone the current player state for comparison.
|
|
158
170
|
const oldPlayer = this ? { ...this } : null;
|
|
159
|
-
|
|
160
|
-
this.manager.options.send(this.guildId, {
|
|
171
|
+
this.manager.sendPacket({
|
|
161
172
|
op: 4,
|
|
162
173
|
d: {
|
|
163
174
|
guild_id: this.guildId,
|
|
@@ -167,49 +178,50 @@ class Player {
|
|
|
167
178
|
},
|
|
168
179
|
});
|
|
169
180
|
// Set the player state to connected.
|
|
170
|
-
this.state =
|
|
181
|
+
this.state = Enums_1.StateTypes.Connected;
|
|
171
182
|
// Emit the player state update event.
|
|
172
|
-
this.manager.emit(
|
|
173
|
-
changeType:
|
|
183
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
184
|
+
changeType: Enums_1.PlayerStateEventTypes.ConnectionChange,
|
|
174
185
|
details: {
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
type: "connection",
|
|
187
|
+
action: "connect",
|
|
188
|
+
previousConnection: oldPlayer?.state === Enums_1.StateTypes.Connected,
|
|
177
189
|
currentConnection: true,
|
|
178
190
|
},
|
|
179
191
|
});
|
|
180
192
|
}
|
|
181
193
|
/**
|
|
182
194
|
* Disconnects the player from the voice channel.
|
|
183
|
-
* @
|
|
184
|
-
* @returns {this} - The current instance of the Player class for method chaining.
|
|
195
|
+
* @returns {this} The player instance.
|
|
185
196
|
*/
|
|
186
197
|
async disconnect() {
|
|
187
198
|
// Set the player state to disconnecting.
|
|
188
|
-
this.state =
|
|
199
|
+
this.state = Enums_1.StateTypes.Disconnecting;
|
|
189
200
|
// Clone the current player state for comparison.
|
|
190
201
|
const oldPlayer = this ? { ...this } : null;
|
|
191
202
|
// Pause the player.
|
|
192
203
|
await this.pause(true);
|
|
193
204
|
// Send the voice state update to the gateway.
|
|
194
|
-
this.manager.
|
|
205
|
+
this.manager.sendPacket({
|
|
195
206
|
op: 4,
|
|
196
207
|
d: {
|
|
197
208
|
guild_id: this.guildId,
|
|
198
209
|
channel_id: null,
|
|
199
|
-
self_mute:
|
|
200
|
-
self_deaf:
|
|
210
|
+
self_mute: null,
|
|
211
|
+
self_deaf: null,
|
|
201
212
|
},
|
|
202
213
|
});
|
|
203
214
|
// Set the player voice channel to null.
|
|
204
215
|
this.voiceChannelId = null;
|
|
205
216
|
// Set the player state to disconnected.
|
|
206
|
-
this.state =
|
|
217
|
+
this.state = Enums_1.StateTypes.Disconnected;
|
|
207
218
|
// Emit the player state update event.
|
|
208
|
-
this.manager.emit(
|
|
209
|
-
changeType:
|
|
219
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
220
|
+
changeType: Enums_1.PlayerStateEventTypes.ConnectionChange,
|
|
210
221
|
details: {
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
type: "connection",
|
|
223
|
+
action: "disconnect",
|
|
224
|
+
previousConnection: oldPlayer.state === Enums_1.StateTypes.Connected,
|
|
213
225
|
currentConnection: false,
|
|
214
226
|
},
|
|
215
227
|
});
|
|
@@ -223,21 +235,22 @@ class Player {
|
|
|
223
235
|
* @emits {PlayerStateUpdate} - Emitted when the player state is updated.
|
|
224
236
|
*/
|
|
225
237
|
async destroy(disconnect = true) {
|
|
226
|
-
|
|
227
|
-
this.state = Utils_1.StateTypes.Destroying;
|
|
238
|
+
this.state = Enums_1.StateTypes.Destroying;
|
|
228
239
|
if (disconnect) {
|
|
229
|
-
await this.disconnect()
|
|
240
|
+
await this.disconnect().catch((err) => {
|
|
241
|
+
console.warn(`[Player#destroy] Failed to disconnect player ${this.guildId}:`, err);
|
|
242
|
+
});
|
|
230
243
|
}
|
|
231
|
-
await this.node.rest.destroyPlayer(this.guildId)
|
|
232
|
-
|
|
233
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, null, {
|
|
234
|
-
changeType: Manager_1.PlayerStateEventTypes.PlayerDestroy,
|
|
244
|
+
await this.node.rest.destroyPlayer(this.guildId).catch((err) => {
|
|
245
|
+
console.warn(`[Player#destroy] REST failed to destroy player ${this.guildId}:`, err);
|
|
235
246
|
});
|
|
236
|
-
this.
|
|
247
|
+
await this.queue.clear();
|
|
248
|
+
await this.queue.clearPrevious();
|
|
249
|
+
await this.queue.setCurrent(null);
|
|
250
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerDestroy, this);
|
|
237
251
|
const deleted = this.manager.players.delete(this.guildId);
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
}
|
|
252
|
+
if (this.manager.options.stateStorage.deleteInactivePlayers)
|
|
253
|
+
await this.manager.cleanupInactivePlayer(this.guildId);
|
|
241
254
|
return deleted;
|
|
242
255
|
}
|
|
243
256
|
/**
|
|
@@ -254,12 +267,14 @@ class Player {
|
|
|
254
267
|
const oldPlayer = this ? { ...this } : null;
|
|
255
268
|
// Update the player voice channel
|
|
256
269
|
this.voiceChannelId = channel;
|
|
270
|
+
this.options.voiceChannelId = channel;
|
|
257
271
|
this.connect();
|
|
258
272
|
// Emit a player state update event
|
|
259
|
-
this.manager.emit(
|
|
260
|
-
changeType:
|
|
273
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
274
|
+
changeType: Enums_1.PlayerStateEventTypes.ChannelChange,
|
|
261
275
|
details: {
|
|
262
|
-
|
|
276
|
+
type: "channel",
|
|
277
|
+
action: "voice",
|
|
263
278
|
previousChannel: oldPlayer.voiceChannelId || null,
|
|
264
279
|
currentChannel: this.voiceChannelId,
|
|
265
280
|
},
|
|
@@ -284,11 +299,13 @@ class Player {
|
|
|
284
299
|
const oldPlayer = this ? { ...this } : null;
|
|
285
300
|
// Update the text channel property
|
|
286
301
|
this.textChannelId = channel;
|
|
302
|
+
this.options.textChannelId = channel;
|
|
287
303
|
// Emit a player state update event with channel change details
|
|
288
|
-
this.manager.emit(
|
|
289
|
-
changeType:
|
|
304
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
305
|
+
changeType: Enums_1.PlayerStateEventTypes.ChannelChange,
|
|
290
306
|
details: {
|
|
291
|
-
|
|
307
|
+
type: "channel",
|
|
308
|
+
action: "text",
|
|
292
309
|
previousChannel: oldPlayer.textChannelId || null,
|
|
293
310
|
currentChannel: this.textChannelId,
|
|
294
311
|
},
|
|
@@ -364,9 +381,11 @@ class Player {
|
|
|
364
381
|
this.set("Internal_BotUser", null);
|
|
365
382
|
}
|
|
366
383
|
const oldPlayer = { ...this };
|
|
367
|
-
this.manager.emit(
|
|
368
|
-
changeType:
|
|
384
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
385
|
+
changeType: Enums_1.PlayerStateEventTypes.AutoPlayChange,
|
|
369
386
|
details: {
|
|
387
|
+
type: "autoplay",
|
|
388
|
+
action: "toggle",
|
|
370
389
|
previousAutoplay: oldPlayer.isAutoplay,
|
|
371
390
|
currentAutoplay: this.isAutoplay,
|
|
372
391
|
},
|
|
@@ -391,23 +410,28 @@ class Player {
|
|
|
391
410
|
* @emits {PlayerStateUpdate} - Emitted when the volume is changed.
|
|
392
411
|
* @example
|
|
393
412
|
* player.setVolume(50);
|
|
413
|
+
* player.setVolume(50, { gradual: true, interval: 50, step: 5 });
|
|
394
414
|
*/
|
|
395
415
|
async setVolume(volume) {
|
|
396
416
|
if (isNaN(volume))
|
|
397
417
|
throw new TypeError("Volume must be a number.");
|
|
398
418
|
if (volume < 0 || volume > 1000)
|
|
399
419
|
throw new RangeError("Volume must be between 0 and 1000.");
|
|
400
|
-
const
|
|
420
|
+
const oldVolume = this.volume;
|
|
421
|
+
const oldPlayer = { ...this };
|
|
401
422
|
await this.node.rest.updatePlayer({
|
|
402
423
|
guildId: this.options.guildId,
|
|
403
|
-
data: {
|
|
404
|
-
volume,
|
|
405
|
-
},
|
|
424
|
+
data: { volume },
|
|
406
425
|
});
|
|
407
426
|
this.volume = volume;
|
|
408
|
-
this.manager.emit(
|
|
409
|
-
changeType:
|
|
410
|
-
details: {
|
|
427
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
428
|
+
changeType: Enums_1.PlayerStateEventTypes.VolumeChange,
|
|
429
|
+
details: {
|
|
430
|
+
type: "volume",
|
|
431
|
+
action: "adjust",
|
|
432
|
+
previousVolume: oldVolume,
|
|
433
|
+
currentVolume: this.volume,
|
|
434
|
+
},
|
|
411
435
|
});
|
|
412
436
|
return this;
|
|
413
437
|
}
|
|
@@ -416,7 +440,7 @@ class Player {
|
|
|
416
440
|
* @param {SponsorBlockSegment[]} segments - The sponsorblock segments to set. Defaults to `[SponsorBlockSegment.Sponsor, SponsorBlockSegment.SelfPromo]` if not provided.
|
|
417
441
|
* @returns {Promise<void>} The promise is resolved when the operation is complete.
|
|
418
442
|
*/
|
|
419
|
-
async setSponsorBlock(segments = [
|
|
443
|
+
async setSponsorBlock(segments = [Enums_1.SponsorBlockSegment.Sponsor, Enums_1.SponsorBlockSegment.SelfPromo]) {
|
|
420
444
|
return this.node.setSponsorBlock(this, segments);
|
|
421
445
|
}
|
|
422
446
|
/**
|
|
@@ -461,10 +485,11 @@ class Player {
|
|
|
461
485
|
this.dynamicRepeat = false;
|
|
462
486
|
}
|
|
463
487
|
// Emit an event indicating the repeat mode has changed
|
|
464
|
-
this.manager.emit(
|
|
465
|
-
changeType:
|
|
466
|
-
|
|
467
|
-
|
|
488
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
489
|
+
changeType: Enums_1.PlayerStateEventTypes.RepeatChange,
|
|
490
|
+
details: {
|
|
491
|
+
type: "repeat",
|
|
492
|
+
action: "track",
|
|
468
493
|
previousRepeat: this.getRepeatState(oldPlayer),
|
|
469
494
|
currentRepeat: this.getRepeatState(this),
|
|
470
495
|
},
|
|
@@ -495,10 +520,11 @@ class Player {
|
|
|
495
520
|
this.dynamicRepeat = false;
|
|
496
521
|
}
|
|
497
522
|
// Emit the player state update event
|
|
498
|
-
this.manager.emit(
|
|
499
|
-
changeType:
|
|
500
|
-
|
|
501
|
-
|
|
523
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
524
|
+
changeType: Enums_1.PlayerStateEventTypes.RepeatChange,
|
|
525
|
+
details: {
|
|
526
|
+
type: "repeat",
|
|
527
|
+
action: "queue",
|
|
502
528
|
previousRepeat: this.getRepeatState(oldPlayer),
|
|
503
529
|
currentRepeat: this.getRepeatState(this),
|
|
504
530
|
},
|
|
@@ -551,10 +577,11 @@ class Player {
|
|
|
551
577
|
this.dynamicRepeat = false;
|
|
552
578
|
}
|
|
553
579
|
// Emit a player state update event
|
|
554
|
-
this.manager.emit(
|
|
555
|
-
changeType:
|
|
556
|
-
|
|
557
|
-
|
|
580
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
581
|
+
changeType: Enums_1.PlayerStateEventTypes.RepeatChange,
|
|
582
|
+
details: {
|
|
583
|
+
type: "repeat",
|
|
584
|
+
action: "dynamic",
|
|
558
585
|
previousRepeat: this.getRepeatState(oldPlayer),
|
|
559
586
|
currentRepeat: this.getRepeatState(this),
|
|
560
587
|
},
|
|
@@ -605,10 +632,11 @@ class Player {
|
|
|
605
632
|
encodedTrack: null,
|
|
606
633
|
},
|
|
607
634
|
});
|
|
608
|
-
this.manager.emit(
|
|
609
|
-
changeType:
|
|
635
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
636
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
610
637
|
details: {
|
|
611
|
-
|
|
638
|
+
type: "queue",
|
|
639
|
+
action: "remove",
|
|
612
640
|
tracks: removedTracks,
|
|
613
641
|
},
|
|
614
642
|
});
|
|
@@ -640,9 +668,11 @@ class Player {
|
|
|
640
668
|
},
|
|
641
669
|
});
|
|
642
670
|
// Emit an event indicating the pause state has changed.
|
|
643
|
-
this.manager.emit(
|
|
644
|
-
changeType:
|
|
671
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
672
|
+
changeType: Enums_1.PlayerStateEventTypes.PauseChange,
|
|
645
673
|
details: {
|
|
674
|
+
type: "pause",
|
|
675
|
+
action: "pause",
|
|
646
676
|
previousPause: oldPlayer.paused,
|
|
647
677
|
currentPause: this.paused,
|
|
648
678
|
},
|
|
@@ -656,31 +686,26 @@ class Player {
|
|
|
656
686
|
* @emits {PlayerStateUpdate} - With {@link PlayerStateEventTypes.TrackChange} as the change type.
|
|
657
687
|
*/
|
|
658
688
|
async previous() {
|
|
659
|
-
//
|
|
660
|
-
|
|
689
|
+
// Get and remove the most recent previous track
|
|
690
|
+
const lastTrack = await this.queue.popPrevious();
|
|
691
|
+
if (!lastTrack) {
|
|
692
|
+
await this.queue.clearPrevious();
|
|
661
693
|
throw new Error("No previous track available.");
|
|
662
694
|
}
|
|
663
695
|
// Capture the current state of the player before making changes.
|
|
664
696
|
const oldPlayer = { ...this };
|
|
665
|
-
//
|
|
666
|
-
// let currentTrackBeforeChange: Track | null = this.queue.current ? (this.queue.current as Track) : null;
|
|
667
|
-
// Get the last played track and remove it from the history
|
|
668
|
-
const lastTrack = (await this.queue.getPrevious()).pop();
|
|
669
|
-
// Set the skip flag to true to prevent the onTrackEnd event from playing the next track.
|
|
697
|
+
// Set skip flag so trackEnd doesn't add current to previous
|
|
670
698
|
this.set("skipFlag", true);
|
|
671
699
|
await this.play(lastTrack);
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
676
|
-
changeType: Manager_1.PlayerStateEventTypes.TrackChange,
|
|
700
|
+
// Emit state update
|
|
701
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
702
|
+
changeType: Enums_1.PlayerStateEventTypes.TrackChange,
|
|
677
703
|
details: {
|
|
678
|
-
|
|
704
|
+
type: "track",
|
|
705
|
+
action: "previous",
|
|
679
706
|
track: lastTrack,
|
|
680
707
|
},
|
|
681
708
|
});
|
|
682
|
-
// Reset the skip flag.
|
|
683
|
-
this.set("skipFlag", false);
|
|
684
709
|
return this;
|
|
685
710
|
}
|
|
686
711
|
/**
|
|
@@ -714,10 +739,11 @@ class Player {
|
|
|
714
739
|
},
|
|
715
740
|
});
|
|
716
741
|
// Emit an event to notify the manager of the track change.
|
|
717
|
-
this.manager.emit(
|
|
718
|
-
changeType:
|
|
742
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
|
|
743
|
+
changeType: Enums_1.PlayerStateEventTypes.TrackChange,
|
|
719
744
|
details: {
|
|
720
|
-
|
|
745
|
+
type: "track",
|
|
746
|
+
action: "timeUpdate",
|
|
721
747
|
previousTime: oldPlayer.position,
|
|
722
748
|
currentTime: this.position,
|
|
723
749
|
},
|
|
@@ -761,13 +787,22 @@ class Player {
|
|
|
761
787
|
const node = this.manager.nodes.get(identifier);
|
|
762
788
|
if (!node)
|
|
763
789
|
throw new Error(`Node with identifier ${identifier} not found`);
|
|
790
|
+
if (this.state !== Enums_1.StateTypes.Connected) {
|
|
791
|
+
return this;
|
|
792
|
+
}
|
|
764
793
|
if (node.options.identifier === this.node.options.identifier) {
|
|
765
794
|
return this;
|
|
766
795
|
}
|
|
767
796
|
try {
|
|
768
797
|
const playerPosition = this.position;
|
|
769
|
-
const { sessionId, event: { token, endpoint }, } = this.voiceState;
|
|
770
798
|
const currentTrack = (await this.queue.getCurrent()) ? await this.queue.getCurrent() : null;
|
|
799
|
+
// Safely get voice state properties with null checks
|
|
800
|
+
const sessionId = this.voiceState?.sessionId;
|
|
801
|
+
const token = this.voiceState?.event?.token;
|
|
802
|
+
const endpoint = this.voiceState?.event?.endpoint;
|
|
803
|
+
if (!sessionId || !token || !endpoint) {
|
|
804
|
+
throw new Error(`Voice state is not properly initialized for player ${this.guildId}. The bot might not be connected to a voice channel.`);
|
|
805
|
+
}
|
|
771
806
|
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
772
807
|
this.manager.players.delete(this.guildId);
|
|
773
808
|
this.node = node;
|
|
@@ -798,7 +833,7 @@ class Player {
|
|
|
798
833
|
if (!newOptions.textChannelId)
|
|
799
834
|
throw new Error("Text channel ID is required");
|
|
800
835
|
// Check if a player already exists for the new guild
|
|
801
|
-
let newPlayer =
|
|
836
|
+
let newPlayer = this.manager.getPlayer(newOptions.guildId);
|
|
802
837
|
// If the player already exists and force is false, return the existing player
|
|
803
838
|
if (newPlayer && !force)
|
|
804
839
|
return newPlayer;
|
|
@@ -826,12 +861,12 @@ class Player {
|
|
|
826
861
|
if (force && newPlayer) {
|
|
827
862
|
await newPlayer.destroy();
|
|
828
863
|
}
|
|
829
|
-
newOptions.
|
|
864
|
+
newOptions.nodeIdentifier = newOptions.nodeIdentifier ?? this.options.nodeIdentifier;
|
|
830
865
|
newOptions.selfDeafen = newOptions.selfDeafen ?? oldPlayerProperties.selfDeafen;
|
|
831
866
|
newOptions.selfMute = newOptions.selfMute ?? oldPlayerProperties.selfMute;
|
|
832
867
|
newOptions.volume = newOptions.volume ?? oldPlayerProperties.volume;
|
|
833
868
|
// Deep clone the current player
|
|
834
|
-
const clonedPlayer =
|
|
869
|
+
const clonedPlayer = this.manager.create(newOptions);
|
|
835
870
|
// Connect the cloned player to the new voice channel
|
|
836
871
|
clonedPlayer.connect();
|
|
837
872
|
// Update the player's state on the Lavalink node
|
|
@@ -871,7 +906,7 @@ class Player {
|
|
|
871
906
|
queueSize: clonedPlayer.queue.size,
|
|
872
907
|
},
|
|
873
908
|
};
|
|
874
|
-
this.manager.emit(
|
|
909
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Transferred player to a new server: ${JSON.stringify(debugInfo)}.`);
|
|
875
910
|
// Return the cloned player
|
|
876
911
|
return clonedPlayer;
|
|
877
912
|
}
|
|
@@ -901,5 +936,133 @@ class Player {
|
|
|
901
936
|
}
|
|
902
937
|
return result;
|
|
903
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Sets up the voice receiver for the player.
|
|
941
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is set up.
|
|
942
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
943
|
+
*/
|
|
944
|
+
async setupVoiceReceiver() {
|
|
945
|
+
if (!this.node.isNodeLink)
|
|
946
|
+
throw new Error("This function is only available for NodeLinks");
|
|
947
|
+
if (this.voiceReceiverWsClient)
|
|
948
|
+
await this.removeVoiceReceiver();
|
|
949
|
+
const headers = {
|
|
950
|
+
Authorization: this.node.options.password,
|
|
951
|
+
"User-Id": this.manager.options.clientId,
|
|
952
|
+
"Guild-Id": this.guildId,
|
|
953
|
+
"Client-Name": this.manager.options.clientName,
|
|
954
|
+
};
|
|
955
|
+
const { host, useSSL, port } = this.node.options;
|
|
956
|
+
this.voiceReceiverWsClient = new ws_1.WebSocket(`${useSSL ? "wss" : "ws"}://${host}:${port}/connection/data`, { headers });
|
|
957
|
+
this.voiceReceiverWsClient.on("open", () => this.openVoiceReceiver());
|
|
958
|
+
this.voiceReceiverWsClient.on("error", (err) => this.onVoiceReceiverError(err));
|
|
959
|
+
this.voiceReceiverWsClient.on("message", (data) => this.onVoiceReceiverMessage(data.toString()));
|
|
960
|
+
this.voiceReceiverWsClient.on("close", (code, reason) => this.closeVoiceReceiver(code, reason.toString()));
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Removes the voice receiver for the player.
|
|
964
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is removed.
|
|
965
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
966
|
+
*/
|
|
967
|
+
async removeVoiceReceiver() {
|
|
968
|
+
if (!this.node.isNodeLink)
|
|
969
|
+
throw new Error("This function is only available for NodeLinks");
|
|
970
|
+
if (this.voiceReceiverWsClient) {
|
|
971
|
+
this.voiceReceiverWsClient.close(1000, "destroy");
|
|
972
|
+
this.voiceReceiverWsClient.removeAllListeners();
|
|
973
|
+
this.voiceReceiverWsClient = null;
|
|
974
|
+
}
|
|
975
|
+
this.isConnectToVoiceReceiver = false;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Closes the voice receiver for the player.
|
|
979
|
+
* @param {number} code - The code to close the voice receiver with.
|
|
980
|
+
* @param {string} reason - The reason to close the voice receiver with.
|
|
981
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is closed.
|
|
982
|
+
*/
|
|
983
|
+
async closeVoiceReceiver(code, reason) {
|
|
984
|
+
await this.disconnectVoiceReceiver();
|
|
985
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Closed voice receiver for player ${this.guildId} with code ${code} and reason ${reason}`);
|
|
986
|
+
if (code !== 1000)
|
|
987
|
+
await this.reconnectVoiceReceiver();
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Reconnects the voice receiver for the player.
|
|
991
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is reconnected.
|
|
992
|
+
*/
|
|
993
|
+
async reconnectVoiceReceiver() {
|
|
994
|
+
this.voiceReceiverReconnectTimeout = setTimeout(async () => {
|
|
995
|
+
if (this.voiceReceiverAttempt > this.voiceReceiverReconnectTries)
|
|
996
|
+
throw new Error("Failed to reconnect to voice receiver");
|
|
997
|
+
this.voiceReceiverWsClient?.removeAllListeners();
|
|
998
|
+
this.voiceReceiverWsClient = null;
|
|
999
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Reconnecting to voice receiver for player ${this.guildId}`);
|
|
1000
|
+
await this.setupVoiceReceiver();
|
|
1001
|
+
this.voiceReceiverAttempt++;
|
|
1002
|
+
}, this.node.options.retryDelayMs);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Disconnects the voice receiver for the player.
|
|
1006
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is disconnected.
|
|
1007
|
+
*/
|
|
1008
|
+
async disconnectVoiceReceiver() {
|
|
1009
|
+
if (!this.isConnectToVoiceReceiver)
|
|
1010
|
+
return;
|
|
1011
|
+
this.voiceReceiverWsClient?.close(1000, "destroy");
|
|
1012
|
+
this.voiceReceiverWsClient?.removeAllListeners();
|
|
1013
|
+
this.voiceReceiverWsClient = null;
|
|
1014
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Disconnected from voice receiver for player ${this.guildId}`);
|
|
1015
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverDisconnect, this);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Opens the voice receiver for the player.
|
|
1019
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is opened.
|
|
1020
|
+
*/
|
|
1021
|
+
async openVoiceReceiver() {
|
|
1022
|
+
if (this.voiceReceiverReconnectTimeout)
|
|
1023
|
+
clearTimeout(this.voiceReceiverReconnectTimeout);
|
|
1024
|
+
this.voiceReceiverReconnectTimeout = null;
|
|
1025
|
+
this.isConnectToVoiceReceiver = true;
|
|
1026
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Opened voice receiver for player ${this.guildId}`);
|
|
1027
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverConnect, this);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Handles a voice receiver message.
|
|
1031
|
+
* @param {string} payload - The payload to handle.
|
|
1032
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
|
|
1033
|
+
*/
|
|
1034
|
+
async onVoiceReceiverMessage(payload) {
|
|
1035
|
+
const packet = JSON.parse(payload);
|
|
1036
|
+
if (!packet?.op)
|
|
1037
|
+
return;
|
|
1038
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved a payload: ${JSON.stringify(payload)}`);
|
|
1039
|
+
switch (packet.type) {
|
|
1040
|
+
case "startSpeakingEvent": {
|
|
1041
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverStartSpeaking, this, packet.data);
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
case "endSpeakingEvent": {
|
|
1045
|
+
const data = {
|
|
1046
|
+
...packet.data,
|
|
1047
|
+
data: Buffer.from(packet.data.data, "base64"),
|
|
1048
|
+
};
|
|
1049
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverEndSpeaking, this, data);
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
default: {
|
|
1053
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown payload: ${JSON.stringify(payload)}`);
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Handles a voice receiver error.
|
|
1060
|
+
* @param {Error} error - The error to handle.
|
|
1061
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver error is handled.
|
|
1062
|
+
*/
|
|
1063
|
+
async onVoiceReceiverError(error) {
|
|
1064
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver error for player ${this.guildId}: ${error.message}`);
|
|
1065
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverError, this, error);
|
|
1066
|
+
}
|
|
904
1067
|
}
|
|
905
1068
|
exports.Player = Player;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Plugin = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base abstract class for all plugins.
|
|
6
|
+
* Users must extend this and implement load and unload methods.
|
|
7
|
+
*/
|
|
4
8
|
class Plugin {
|
|
5
9
|
name;
|
|
6
10
|
/**
|
|
@@ -9,6 +13,5 @@ class Plugin {
|
|
|
9
13
|
constructor(name) {
|
|
10
14
|
this.name = name;
|
|
11
15
|
}
|
|
12
|
-
load(manager) { }
|
|
13
16
|
}
|
|
14
17
|
exports.Plugin = Plugin;
|