magmastream 2.9.2-dev.1 → 2.9.2-dev.10
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/dist/index.d.ts +103 -23
- package/dist/index.js +1 -0
- package/dist/statestorage/JsonQueue.js +332 -176
- package/dist/statestorage/MemoryQueue.js +288 -203
- package/dist/statestorage/RedisQueue.js +482 -204
- package/dist/structures/Enums.js +110 -1
- package/dist/structures/Filters.js +27 -13
- package/dist/structures/MagmastreamError.js +19 -0
- package/dist/structures/Manager.js +351 -219
- package/dist/structures/Node.js +227 -66
- package/dist/structures/Player.js +199 -58
- package/dist/structures/Rest.js +23 -12
- package/dist/structures/Utils.js +83 -67
- package/dist/utils/managerCheck.js +99 -21
- package/dist/utils/nodeCheck.js +59 -34
- package/dist/utils/playerCheck.js +47 -28
- package/package.json +3 -2
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RedisQueue = void 0;
|
|
4
4
|
const Enums_1 = require("../structures/Enums");
|
|
5
5
|
const Utils_1 = require("../structures/Utils");
|
|
6
|
+
const MagmastreamError_1 = require("../structures/MagmastreamError");
|
|
6
7
|
/**
|
|
7
8
|
* The player's queue, the `current` property is the currently playing track, think of the rest as the up-coming tracks.
|
|
8
9
|
*/
|
|
@@ -26,9 +27,11 @@ class RedisQueue {
|
|
|
26
27
|
this.guildId = guildId;
|
|
27
28
|
this.manager = manager;
|
|
28
29
|
this.redis = manager.redis;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const rawPrefix = manager.options.stateStorage.redisConfig.prefix;
|
|
31
|
+
let clean = typeof rawPrefix === "string" ? rawPrefix.trim() : "";
|
|
32
|
+
if (!clean.endsWith(":"))
|
|
33
|
+
clean = clean || "magmastream";
|
|
34
|
+
this.redisPrefix = `${clean}:`;
|
|
32
35
|
}
|
|
33
36
|
// #region Public
|
|
34
37
|
/**
|
|
@@ -37,75 +40,142 @@ class RedisQueue {
|
|
|
37
40
|
* @param [offset=null] The position to add the track(s) at. If not provided, the track(s) will be added at the end of the queue.
|
|
38
41
|
*/
|
|
39
42
|
async add(track, offset) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
43
|
+
try {
|
|
44
|
+
const isArray = Array.isArray(track);
|
|
45
|
+
const tracks = isArray ? track : [track];
|
|
46
|
+
// Serialize tracks
|
|
47
|
+
const serialized = tracks.map((t) => this.serialize(t));
|
|
48
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
49
|
+
// Set current track if none exists
|
|
50
|
+
if (!(await this.getCurrent())) {
|
|
51
|
+
const current = serialized.shift();
|
|
52
|
+
if (current) {
|
|
53
|
+
await this.setCurrent(this.deserialize(current));
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// Insert at offset or append
|
|
57
|
+
try {
|
|
58
|
+
if (typeof offset === "number" && !isNaN(offset)) {
|
|
59
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
60
|
+
queue.splice(offset, 0, ...serialized);
|
|
61
|
+
await this.redis.del(this.queueKey);
|
|
62
|
+
if (queue.length > 0) {
|
|
63
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (serialized.length > 0) {
|
|
67
|
+
await this.redis.rpush(this.queueKey, ...serialized);
|
|
68
|
+
}
|
|
57
69
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const AutoplayUser = (await this.manager.players.get(this.guildId).get("Internal_AutoplayUser"));
|
|
66
|
-
if (AutoplayUser && AutoplayUser.id === track.requester.id) {
|
|
67
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
68
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
69
|
-
details: {
|
|
70
|
-
type: "queue",
|
|
71
|
-
action: "autoPlayAdd",
|
|
72
|
-
tracks: Array.isArray(track) ? track : [track],
|
|
73
|
-
},
|
|
70
|
+
catch (err) {
|
|
71
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
72
|
+
? err
|
|
73
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
74
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
75
|
+
message: `Failed to add tracks to Redis queue for guild ${this.guildId}: ${err.message}`,
|
|
76
|
+
cause: err,
|
|
74
77
|
});
|
|
75
|
-
|
|
78
|
+
console.error(error);
|
|
79
|
+
}
|
|
80
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] Added ${tracks.length} track(s) to queue`);
|
|
81
|
+
// Autoplay logic
|
|
82
|
+
if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
|
|
83
|
+
if (!Array.isArray(track)) {
|
|
84
|
+
const AutoplayUser = (await this.manager.players.get(this.guildId).get("Internal_AutoplayUser"));
|
|
85
|
+
if (AutoplayUser && AutoplayUser.id === track.requester.id) {
|
|
86
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
87
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
88
|
+
details: {
|
|
89
|
+
type: "queue",
|
|
90
|
+
action: "autoPlayAdd",
|
|
91
|
+
tracks: [track],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
76
96
|
}
|
|
77
97
|
}
|
|
98
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
99
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
100
|
+
details: {
|
|
101
|
+
type: "queue",
|
|
102
|
+
action: "add",
|
|
103
|
+
tracks,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
109
|
+
? err
|
|
110
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
111
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
112
|
+
message: `Unexpected error in add() for guild ${this.guildId}: ${err.message}`,
|
|
113
|
+
cause: err,
|
|
114
|
+
});
|
|
115
|
+
console.error(error);
|
|
78
116
|
}
|
|
79
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
80
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
81
|
-
details: {
|
|
82
|
-
type: "queue",
|
|
83
|
-
action: "add",
|
|
84
|
-
tracks,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
119
|
* Adds a track or tracks to the previous tracks.
|
|
90
120
|
* @param track The track or tracks to add.
|
|
91
121
|
*/
|
|
92
122
|
async addPrevious(track) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
try {
|
|
124
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
125
|
+
if (!tracks.length) {
|
|
126
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
127
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
128
|
+
message: `No tracks provided for addPrevious in guild ${this.guildId}`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const serialized = tracks.map(this.serialize);
|
|
132
|
+
try {
|
|
133
|
+
// Push newest to TAIL
|
|
134
|
+
await this.redis.rpush(this.previousKey, ...serialized);
|
|
135
|
+
// Keep only the most recent maxPreviousTracks (trim from HEAD)
|
|
136
|
+
const max = this.manager.options.maxPreviousTracks;
|
|
137
|
+
await this.redis.ltrim(this.previousKey, -max, -1);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
141
|
+
? err
|
|
142
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
143
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
144
|
+
message: `Failed to add previous tracks to Redis for guild ${this.guildId}: ${err.message}`,
|
|
145
|
+
cause: err,
|
|
146
|
+
});
|
|
147
|
+
console.error(error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
152
|
+
? err
|
|
153
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
154
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
155
|
+
message: `Unexpected error in addPrevious() for guild ${this.guildId}: ${err.message}`,
|
|
156
|
+
cause: err,
|
|
157
|
+
});
|
|
158
|
+
console.error(error);
|
|
159
|
+
}
|
|
102
160
|
}
|
|
103
161
|
/**
|
|
104
162
|
* Clears the queue.
|
|
105
163
|
*/
|
|
106
164
|
async clear() {
|
|
107
165
|
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
108
|
-
|
|
166
|
+
try {
|
|
167
|
+
await this.redis.del(this.queueKey);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
171
|
+
? err
|
|
172
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
173
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
174
|
+
message: `Failed to clear queue for guild ${this.guildId}: ${err.message}`,
|
|
175
|
+
cause: err,
|
|
176
|
+
});
|
|
177
|
+
console.error(error);
|
|
178
|
+
}
|
|
109
179
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
110
180
|
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
111
181
|
details: {
|
|
@@ -114,47 +184,97 @@ class RedisQueue {
|
|
|
114
184
|
tracks: [],
|
|
115
185
|
},
|
|
116
186
|
});
|
|
117
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[
|
|
187
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] Cleared the queue for: ${this.guildId}`);
|
|
118
188
|
}
|
|
119
189
|
/**
|
|
120
190
|
* Clears the previous tracks.
|
|
121
191
|
*/
|
|
122
192
|
async clearPrevious() {
|
|
123
|
-
|
|
193
|
+
try {
|
|
194
|
+
await this.redis.del(this.previousKey);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
198
|
+
? err
|
|
199
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
200
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
201
|
+
message: `Failed to clear previous tracks for guild ${this.guildId}: ${err.message}`,
|
|
202
|
+
cause: err,
|
|
203
|
+
});
|
|
204
|
+
console.error(error);
|
|
205
|
+
}
|
|
124
206
|
}
|
|
125
207
|
/**
|
|
126
208
|
* Removes the first track from the queue.
|
|
127
209
|
*/
|
|
128
210
|
async dequeue() {
|
|
129
|
-
|
|
130
|
-
|
|
211
|
+
try {
|
|
212
|
+
const raw = await this.redis.lpop(this.queueKey);
|
|
213
|
+
return raw ? this.deserialize(raw) : undefined;
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
217
|
+
? err
|
|
218
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
219
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
220
|
+
message: `Failed to dequeue track for guild ${this.guildId}: ${err.message}`,
|
|
221
|
+
cause: err,
|
|
222
|
+
});
|
|
223
|
+
console.error(error);
|
|
224
|
+
}
|
|
131
225
|
}
|
|
132
226
|
/**
|
|
133
227
|
* @returns The total duration of the queue in milliseconds.
|
|
134
228
|
* This includes the duration of the currently playing track.
|
|
135
229
|
*/
|
|
136
230
|
async duration() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
231
|
+
try {
|
|
232
|
+
const tracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
233
|
+
const currentDuration = (await this.getCurrent())?.duration || 0;
|
|
234
|
+
const total = tracks.reduce((acc, raw) => {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = this.deserialize(raw);
|
|
237
|
+
return acc + (parsed.duration || 0);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
// Skip invalid tracks but log
|
|
241
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] Skipping invalid track during duration calculation for guild ${this.guildId}: ${err.message}`);
|
|
242
|
+
return acc;
|
|
243
|
+
}
|
|
244
|
+
}, currentDuration);
|
|
245
|
+
return total;
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
249
|
+
? err
|
|
250
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
251
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
252
|
+
message: `Failed to calculate total queue duration for guild ${this.guildId}: ${err.message}`,
|
|
253
|
+
cause: err,
|
|
254
|
+
});
|
|
255
|
+
console.error(error);
|
|
256
|
+
}
|
|
149
257
|
}
|
|
150
258
|
/**
|
|
151
259
|
* Adds a track to the front of the queue.
|
|
152
260
|
* @param track The track or tracks to add.
|
|
153
261
|
*/
|
|
154
262
|
async enqueueFront(track) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
263
|
+
try {
|
|
264
|
+
const serialized = Array.isArray(track) ? track.map(this.serialize) : [this.serialize(track)];
|
|
265
|
+
// Redis: LPUSH adds to front, reverse to maintain order if multiple tracks
|
|
266
|
+
await this.redis.lpush(this.queueKey, ...serialized.reverse());
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
270
|
+
? err
|
|
271
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
272
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
273
|
+
message: `Failed to enqueue track to front for guild ${this.guildId}: ${err.message}`,
|
|
274
|
+
cause: err,
|
|
275
|
+
});
|
|
276
|
+
console.error(error);
|
|
277
|
+
}
|
|
158
278
|
}
|
|
159
279
|
/**
|
|
160
280
|
* Whether all tracks in the queue match the specified condition.
|
|
@@ -187,29 +307,77 @@ class RedisQueue {
|
|
|
187
307
|
* @returns The current track.
|
|
188
308
|
*/
|
|
189
309
|
async getCurrent() {
|
|
190
|
-
|
|
191
|
-
|
|
310
|
+
try {
|
|
311
|
+
const raw = await this.redis.get(this.currentKey);
|
|
312
|
+
return raw ? this.deserialize(raw) : null;
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
316
|
+
? err
|
|
317
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
318
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
319
|
+
message: `Failed to get current track for guild ${this.guildId}: ${err.message}`,
|
|
320
|
+
cause: err,
|
|
321
|
+
});
|
|
322
|
+
console.error(error);
|
|
323
|
+
}
|
|
192
324
|
}
|
|
193
325
|
/**
|
|
194
326
|
* @returns The previous tracks.
|
|
195
327
|
*/
|
|
196
328
|
async getPrevious() {
|
|
197
|
-
|
|
198
|
-
|
|
329
|
+
try {
|
|
330
|
+
const raw = await this.redis.lrange(this.previousKey, 0, -1);
|
|
331
|
+
return raw.map(this.deserialize);
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
335
|
+
? err
|
|
336
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
337
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
338
|
+
message: `Failed to get previous tracks for guild ${this.guildId}: ${err.message}`,
|
|
339
|
+
cause: err,
|
|
340
|
+
});
|
|
341
|
+
console.error(error);
|
|
342
|
+
}
|
|
199
343
|
}
|
|
200
344
|
/**
|
|
201
345
|
* @returns The tracks in the queue from the start to the end.
|
|
202
346
|
*/
|
|
203
347
|
async getSlice(start = 0, end = -1) {
|
|
204
|
-
|
|
205
|
-
|
|
348
|
+
try {
|
|
349
|
+
const raw = await this.redis.lrange(this.queueKey, start, end === -1 ? -1 : end - 1);
|
|
350
|
+
return raw.map(this.deserialize);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
354
|
+
? err
|
|
355
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
356
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
357
|
+
message: `Failed to get slice of queue for guild ${this.guildId}: ${err.message}`,
|
|
358
|
+
cause: err,
|
|
359
|
+
});
|
|
360
|
+
console.error(error);
|
|
361
|
+
}
|
|
206
362
|
}
|
|
207
363
|
/**
|
|
208
364
|
* @returns The tracks in the queue.
|
|
209
365
|
*/
|
|
210
366
|
async getTracks() {
|
|
211
|
-
|
|
212
|
-
|
|
367
|
+
try {
|
|
368
|
+
const raw = await this.redis.lrange(this.queueKey, 0, -1);
|
|
369
|
+
return raw.map(this.deserialize);
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
373
|
+
? err
|
|
374
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
375
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
376
|
+
message: `Failed to get tracks for guild ${this.guildId}: ${err.message}`,
|
|
377
|
+
cause: err,
|
|
378
|
+
});
|
|
379
|
+
console.error(error);
|
|
380
|
+
}
|
|
213
381
|
}
|
|
214
382
|
/**
|
|
215
383
|
* Maps the tracks in the queue.
|
|
@@ -227,104 +395,165 @@ class RedisQueue {
|
|
|
227
395
|
* @returns The removed tracks.
|
|
228
396
|
*/
|
|
229
397
|
async modifyAt(start, deleteCount = 0, ...items) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
398
|
+
try {
|
|
399
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
400
|
+
const removed = queue.splice(start, deleteCount, ...items.map(this.serialize));
|
|
401
|
+
await this.redis.del(this.queueKey);
|
|
402
|
+
if (queue.length > 0) {
|
|
403
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
404
|
+
}
|
|
405
|
+
return removed.map(this.deserialize);
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
409
|
+
? err
|
|
410
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
411
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
412
|
+
message: `Failed to modify queue at index ${start} for guild ${this.guildId}: ${err.message}`,
|
|
413
|
+
cause: err,
|
|
414
|
+
});
|
|
415
|
+
console.error(error);
|
|
235
416
|
}
|
|
236
|
-
return removed.map(this.deserialize);
|
|
237
417
|
}
|
|
238
418
|
/**
|
|
239
419
|
* Removes the newest track.
|
|
240
420
|
* @returns The newest track.
|
|
241
421
|
*/
|
|
242
422
|
async popPrevious() {
|
|
243
|
-
|
|
244
|
-
|
|
423
|
+
try {
|
|
424
|
+
// Pop the newest track from the TAIL
|
|
425
|
+
const raw = await this.redis.rpop(this.previousKey);
|
|
426
|
+
return raw ? this.deserialize(raw) : null;
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
430
|
+
? err
|
|
431
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
432
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
433
|
+
message: `Failed to pop previous track for guild ${this.guildId}: ${err.message}`,
|
|
434
|
+
cause: err,
|
|
435
|
+
});
|
|
436
|
+
console.error(error);
|
|
437
|
+
}
|
|
245
438
|
}
|
|
246
439
|
async remove(startOrPos = 0, end) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
440
|
+
try {
|
|
441
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
442
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
443
|
+
let removed = [];
|
|
444
|
+
if (typeof end === "number") {
|
|
445
|
+
if (startOrPos >= end || startOrPos >= queue.length) {
|
|
446
|
+
throw new RangeError("Invalid range.");
|
|
447
|
+
}
|
|
448
|
+
removed = queue.slice(startOrPos, end);
|
|
449
|
+
queue.splice(startOrPos, end - startOrPos);
|
|
253
450
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
451
|
+
else {
|
|
452
|
+
removed = queue.splice(startOrPos, 1);
|
|
453
|
+
}
|
|
454
|
+
await this.redis.del(this.queueKey);
|
|
455
|
+
if (queue.length > 0) {
|
|
456
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
457
|
+
}
|
|
458
|
+
const deserialized = removed.map(this.deserialize);
|
|
459
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] Removed ${removed.length} track(s) from position ${startOrPos}${end ? ` to ${end}` : ""}`);
|
|
460
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
461
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
462
|
+
details: {
|
|
463
|
+
type: "queue",
|
|
464
|
+
action: "remove",
|
|
465
|
+
tracks: deserialized,
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
return deserialized;
|
|
259
469
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
470
|
+
catch (err) {
|
|
471
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
472
|
+
? err
|
|
473
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
474
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
475
|
+
message: `Failed to remove track for guild ${this.guildId}: ${err.message}`,
|
|
476
|
+
cause: err,
|
|
477
|
+
});
|
|
478
|
+
console.error(error);
|
|
263
479
|
}
|
|
264
|
-
const deserialized = removed.map(this.deserialize);
|
|
265
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Removed ${removed.length} track(s) from position ${startOrPos}${end ? ` to ${end}` : ""}`);
|
|
266
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
267
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
268
|
-
details: {
|
|
269
|
-
type: "queue",
|
|
270
|
-
action: "remove",
|
|
271
|
-
tracks: deserialized,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
return deserialized;
|
|
275
480
|
}
|
|
276
481
|
/**
|
|
277
482
|
* Shuffles the queue round-robin style.
|
|
278
483
|
*/
|
|
279
484
|
async roundRobinShuffle() {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
userMap.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Shuffle each user's tracks
|
|
291
|
-
for (const tracks of userMap.values()) {
|
|
292
|
-
for (let i = tracks.length - 1; i > 0; i--) {
|
|
293
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
294
|
-
[tracks[i], tracks[j]] = [tracks[j], tracks[i]];
|
|
485
|
+
try {
|
|
486
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
487
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
488
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
489
|
+
const userMap = new Map();
|
|
490
|
+
for (const track of deserialized) {
|
|
491
|
+
const userId = track.requester.id;
|
|
492
|
+
if (!userMap.has(userId))
|
|
493
|
+
userMap.set(userId, []);
|
|
494
|
+
userMap.get(userId).push(track);
|
|
295
495
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
496
|
+
// Shuffle each user's tracks
|
|
497
|
+
for (const tracks of userMap.values()) {
|
|
498
|
+
for (let i = tracks.length - 1; i > 0; i--) {
|
|
499
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
500
|
+
[tracks[i], tracks[j]] = [tracks[j], tracks[i]];
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const users = [...userMap.keys()];
|
|
504
|
+
const queues = users.map((id) => userMap.get(id));
|
|
505
|
+
const shuffledQueue = [];
|
|
506
|
+
while (queues.some((q) => q.length > 0)) {
|
|
507
|
+
for (const q of queues) {
|
|
508
|
+
const track = q.shift();
|
|
509
|
+
if (track)
|
|
510
|
+
shuffledQueue.push(track);
|
|
511
|
+
}
|
|
305
512
|
}
|
|
513
|
+
await this.redis.del(this.queueKey);
|
|
514
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
515
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
516
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
517
|
+
details: {
|
|
518
|
+
type: "queue",
|
|
519
|
+
action: "roundRobin",
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
526
|
+
? err
|
|
527
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
528
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
529
|
+
message: `Failed to roundRobinShuffle the queue for guild ${this.guildId}: ${err.message}`,
|
|
530
|
+
cause: err,
|
|
531
|
+
});
|
|
532
|
+
console.error(error);
|
|
306
533
|
}
|
|
307
|
-
await this.redis.del(this.queueKey);
|
|
308
|
-
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
309
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
310
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
311
|
-
details: {
|
|
312
|
-
type: "queue",
|
|
313
|
-
action: "roundRobin",
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
|
|
317
534
|
}
|
|
318
535
|
/**
|
|
319
536
|
* Sets the current track.
|
|
320
537
|
* @param track The track to set.
|
|
321
538
|
*/
|
|
322
539
|
async setCurrent(track) {
|
|
323
|
-
|
|
324
|
-
|
|
540
|
+
try {
|
|
541
|
+
if (track) {
|
|
542
|
+
await this.redis.set(this.currentKey, this.serialize(track));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
await this.redis.del(this.currentKey);
|
|
546
|
+
}
|
|
325
547
|
}
|
|
326
|
-
|
|
327
|
-
|
|
548
|
+
catch (err) {
|
|
549
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
550
|
+
? err
|
|
551
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
552
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
553
|
+
message: `Failed to setCurrent the queue for guild ${this.guildId}: ${err.message}`,
|
|
554
|
+
cause: err,
|
|
555
|
+
});
|
|
556
|
+
console.error(error);
|
|
328
557
|
}
|
|
329
558
|
}
|
|
330
559
|
/**
|
|
@@ -332,43 +561,79 @@ class RedisQueue {
|
|
|
332
561
|
* @param track The track to set.
|
|
333
562
|
*/
|
|
334
563
|
async setPrevious(track) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
564
|
+
try {
|
|
565
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
566
|
+
if (!tracks.length)
|
|
567
|
+
return;
|
|
568
|
+
await this.redis
|
|
569
|
+
.multi()
|
|
570
|
+
.del(this.previousKey)
|
|
571
|
+
.rpush(this.previousKey, ...tracks.map(this.serialize))
|
|
572
|
+
.exec();
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
576
|
+
? err
|
|
577
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
578
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
579
|
+
message: `Failed to setPrevious the queue for guild ${this.guildId}: ${err.message}`,
|
|
580
|
+
cause: err,
|
|
581
|
+
});
|
|
582
|
+
console.error(error);
|
|
583
|
+
}
|
|
343
584
|
}
|
|
344
585
|
/**
|
|
345
586
|
* Shuffles the queue.
|
|
346
587
|
*/
|
|
347
588
|
async shuffle() {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
589
|
+
try {
|
|
590
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
591
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
592
|
+
for (let i = queue.length - 1; i > 0; i--) {
|
|
593
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
594
|
+
[queue[i], queue[j]] = [queue[j], queue[i]];
|
|
595
|
+
}
|
|
596
|
+
await this.redis.del(this.queueKey);
|
|
597
|
+
if (queue.length > 0) {
|
|
598
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
599
|
+
}
|
|
600
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
601
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
602
|
+
details: {
|
|
603
|
+
type: "queue",
|
|
604
|
+
action: "shuffle",
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] Shuffled the queue for: ${this.guildId}`);
|
|
353
608
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
609
|
+
catch (err) {
|
|
610
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
611
|
+
? err
|
|
612
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
613
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
614
|
+
message: `Failed to shuffle the queue for guild ${this.guildId}: ${err.message}`,
|
|
615
|
+
cause: err,
|
|
616
|
+
});
|
|
617
|
+
console.error(error);
|
|
357
618
|
}
|
|
358
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
359
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
360
|
-
details: {
|
|
361
|
-
type: "queue",
|
|
362
|
-
action: "shuffle",
|
|
363
|
-
},
|
|
364
|
-
});
|
|
365
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Shuffled the queue for: ${this.guildId}`);
|
|
366
619
|
}
|
|
367
620
|
/**
|
|
368
621
|
* @returns The size of the queue.
|
|
369
622
|
*/
|
|
370
623
|
async size() {
|
|
371
|
-
|
|
624
|
+
try {
|
|
625
|
+
return await this.redis.llen(this.queueKey);
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
629
|
+
? err
|
|
630
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
631
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
632
|
+
message: `Failed to get the size of the queue for guild ${this.guildId}: ${err.message}`,
|
|
633
|
+
cause: err,
|
|
634
|
+
});
|
|
635
|
+
console.error(error);
|
|
636
|
+
}
|
|
372
637
|
}
|
|
373
638
|
/**
|
|
374
639
|
* @returns Whether any tracks in the queue match the specified condition.
|
|
@@ -388,34 +653,46 @@ class RedisQueue {
|
|
|
388
653
|
* Shuffles the queue, but keeps the tracks of the same user together.
|
|
389
654
|
*/
|
|
390
655
|
async userBlockShuffle() {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
userMap.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const shuffledQueue = [];
|
|
402
|
-
while (shuffledQueue.length < deserialized.length) {
|
|
403
|
-
for (const [, tracks] of userMap) {
|
|
404
|
-
const track = tracks.shift();
|
|
405
|
-
if (track)
|
|
406
|
-
shuffledQueue.push(track);
|
|
656
|
+
try {
|
|
657
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
658
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
659
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
660
|
+
const userMap = new Map();
|
|
661
|
+
for (const track of deserialized) {
|
|
662
|
+
const userId = track.requester.id;
|
|
663
|
+
if (!userMap.has(userId))
|
|
664
|
+
userMap.set(userId, []);
|
|
665
|
+
userMap.get(userId).push(track);
|
|
407
666
|
}
|
|
667
|
+
const shuffledQueue = [];
|
|
668
|
+
while (shuffledQueue.length < deserialized.length) {
|
|
669
|
+
for (const [, tracks] of userMap) {
|
|
670
|
+
const track = tracks.shift();
|
|
671
|
+
if (track)
|
|
672
|
+
shuffledQueue.push(track);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
await this.redis.del(this.queueKey);
|
|
676
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
677
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
678
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
679
|
+
details: {
|
|
680
|
+
type: "queue",
|
|
681
|
+
action: "userBlock",
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[REDISQUEUE] userBlockShuffled the queue for: ${this.guildId}`);
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
688
|
+
? err
|
|
689
|
+
: new MagmastreamError_1.MagmaStreamError({
|
|
690
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
691
|
+
message: `Failed to userBlockShuffle the queue for guild ${this.guildId}: ${err.message}`,
|
|
692
|
+
cause: err,
|
|
693
|
+
});
|
|
694
|
+
console.error(error);
|
|
408
695
|
}
|
|
409
|
-
await this.redis.del(this.queueKey);
|
|
410
|
-
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
411
|
-
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
412
|
-
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
413
|
-
details: {
|
|
414
|
-
type: "queue",
|
|
415
|
-
action: "userBlock",
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] userBlockShuffled the queue for: ${this.guildId}`);
|
|
419
696
|
}
|
|
420
697
|
// #endregion Public
|
|
421
698
|
// #region Private
|
|
@@ -429,7 +706,8 @@ class RedisQueue {
|
|
|
429
706
|
* Deserializes a track from a string.
|
|
430
707
|
*/
|
|
431
708
|
deserialize(data) {
|
|
432
|
-
|
|
709
|
+
const track = JSON.parse(data);
|
|
710
|
+
return Utils_1.TrackUtils.revive(track);
|
|
433
711
|
}
|
|
434
712
|
/**
|
|
435
713
|
* @returns The previous key.
|