magmastream 2.9.2-dev.1 → 2.9.2-dev.2
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 +63 -4
- package/dist/index.js +1 -0
- package/dist/statestorage/JsonQueue.js +273 -171
- package/dist/statestorage/MemoryQueue.js +260 -203
- package/dist/statestorage/RedisQueue.js +396 -203
- 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 +345 -219
- package/dist/structures/Node.js +222 -64
- package/dist/structures/Player.js +169 -56
- package/dist/structures/Rest.js +23 -12
- package/dist/structures/Utils.js +66 -65
- package/dist/utils/managerCheck.js +99 -21
- package/dist/utils/nodeCheck.js +59 -34
- package/dist/utils/playerCheck.js +47 -28
- package/package.json +1 -1
|
@@ -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,126 @@ 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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
catch (err) {
|
|
71
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
72
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
73
|
+
message: `Failed to add tracks to Redis queue for guild ${this.guildId}: ${err.message}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Added ${tracks.length} track(s) to queue`);
|
|
77
|
+
// Autoplay logic
|
|
78
|
+
if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
|
|
79
|
+
if (!Array.isArray(track)) {
|
|
80
|
+
const AutoplayUser = (await this.manager.players.get(this.guildId).get("Internal_AutoplayUser"));
|
|
81
|
+
if (AutoplayUser && AutoplayUser.id === track.requester.id) {
|
|
82
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
83
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
84
|
+
details: {
|
|
85
|
+
type: "queue",
|
|
86
|
+
action: "autoPlayAdd",
|
|
87
|
+
tracks: [track],
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
76
92
|
}
|
|
77
93
|
}
|
|
94
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
95
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
96
|
+
details: {
|
|
97
|
+
type: "queue",
|
|
98
|
+
action: "add",
|
|
99
|
+
tracks,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err instanceof MagmastreamError_1.MagmaStreamError)
|
|
105
|
+
throw err;
|
|
106
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
107
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
108
|
+
message: `Unexpected error in add() for guild ${this.guildId}: ${err.message}`,
|
|
109
|
+
});
|
|
78
110
|
}
|
|
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
111
|
}
|
|
88
112
|
/**
|
|
89
113
|
* Adds a track or tracks to the previous tracks.
|
|
90
114
|
* @param track The track or tracks to add.
|
|
91
115
|
*/
|
|
92
116
|
async addPrevious(track) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
117
|
+
try {
|
|
118
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
119
|
+
if (!tracks.length) {
|
|
120
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
121
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
122
|
+
message: `No tracks provided for addPrevious in guild ${this.guildId}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const serialized = tracks.map(this.serialize);
|
|
126
|
+
try {
|
|
127
|
+
// Push newest to TAIL
|
|
128
|
+
await this.redis.rpush(this.previousKey, ...serialized);
|
|
129
|
+
// Keep only the most recent maxPreviousTracks (trim from HEAD)
|
|
130
|
+
const max = this.manager.options.maxPreviousTracks;
|
|
131
|
+
await this.redis.ltrim(this.previousKey, -max, -1);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
135
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
136
|
+
message: `Failed to add previous tracks to Redis for guild ${this.guildId}: ${err.message}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
if (err instanceof MagmastreamError_1.MagmaStreamError)
|
|
142
|
+
throw err;
|
|
143
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
144
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
145
|
+
message: `Unexpected error in addPrevious() for guild ${this.guildId}: ${err.message}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
102
148
|
}
|
|
103
149
|
/**
|
|
104
150
|
* Clears the queue.
|
|
105
151
|
*/
|
|
106
152
|
async clear() {
|
|
107
153
|
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
108
|
-
|
|
154
|
+
try {
|
|
155
|
+
await this.redis.del(this.queueKey);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
159
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
160
|
+
message: `Failed to clear queue for guild ${this.guildId}: ${err.message}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
109
163
|
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
110
164
|
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
111
165
|
details: {
|
|
@@ -120,41 +174,75 @@ class RedisQueue {
|
|
|
120
174
|
* Clears the previous tracks.
|
|
121
175
|
*/
|
|
122
176
|
async clearPrevious() {
|
|
123
|
-
|
|
177
|
+
try {
|
|
178
|
+
await this.redis.del(this.previousKey);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
182
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
183
|
+
message: `Failed to clear previous tracks for guild ${this.guildId}: ${err.message}`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
124
186
|
}
|
|
125
187
|
/**
|
|
126
188
|
* Removes the first track from the queue.
|
|
127
189
|
*/
|
|
128
190
|
async dequeue() {
|
|
129
|
-
|
|
130
|
-
|
|
191
|
+
try {
|
|
192
|
+
const raw = await this.redis.lpop(this.queueKey);
|
|
193
|
+
return raw ? this.deserialize(raw) : undefined;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
197
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
198
|
+
message: `Failed to dequeue track for guild ${this.guildId}: ${err.message}`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
131
201
|
}
|
|
132
202
|
/**
|
|
133
203
|
* @returns The total duration of the queue in milliseconds.
|
|
134
204
|
* This includes the duration of the currently playing track.
|
|
135
205
|
*/
|
|
136
206
|
async duration() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
207
|
+
try {
|
|
208
|
+
const tracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
209
|
+
const currentDuration = (await this.getCurrent())?.duration || 0;
|
|
210
|
+
const total = tracks.reduce((acc, raw) => {
|
|
211
|
+
try {
|
|
212
|
+
const parsed = this.deserialize(raw);
|
|
213
|
+
return acc + (parsed.duration || 0);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
// Skip invalid tracks but log
|
|
217
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Skipping invalid track during duration calculation for guild ${this.guildId}: ${err.message}`);
|
|
218
|
+
return acc;
|
|
219
|
+
}
|
|
220
|
+
}, currentDuration);
|
|
221
|
+
return total;
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
225
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
226
|
+
message: `Failed to calculate total queue duration for guild ${this.guildId}: ${err.message}`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
149
229
|
}
|
|
150
230
|
/**
|
|
151
231
|
* Adds a track to the front of the queue.
|
|
152
232
|
* @param track The track or tracks to add.
|
|
153
233
|
*/
|
|
154
234
|
async enqueueFront(track) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
235
|
+
try {
|
|
236
|
+
const serialized = Array.isArray(track) ? track.map(this.serialize) : [this.serialize(track)];
|
|
237
|
+
// Redis: LPUSH adds to front, reverse to maintain order if multiple tracks
|
|
238
|
+
await this.redis.lpush(this.queueKey, ...serialized.reverse());
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
242
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
243
|
+
message: `Failed to enqueue track to front for guild ${this.guildId}: ${err.message}`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
158
246
|
}
|
|
159
247
|
/**
|
|
160
248
|
* Whether all tracks in the queue match the specified condition.
|
|
@@ -187,29 +275,61 @@ class RedisQueue {
|
|
|
187
275
|
* @returns The current track.
|
|
188
276
|
*/
|
|
189
277
|
async getCurrent() {
|
|
190
|
-
|
|
191
|
-
|
|
278
|
+
try {
|
|
279
|
+
const raw = await this.redis.get(this.currentKey);
|
|
280
|
+
return raw ? this.deserialize(raw) : null;
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
284
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
285
|
+
message: `Failed to get current track for guild ${this.guildId}: ${err.message}`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
192
288
|
}
|
|
193
289
|
/**
|
|
194
290
|
* @returns The previous tracks.
|
|
195
291
|
*/
|
|
196
292
|
async getPrevious() {
|
|
197
|
-
|
|
198
|
-
|
|
293
|
+
try {
|
|
294
|
+
const raw = await this.redis.lrange(this.previousKey, 0, -1);
|
|
295
|
+
return raw.map(this.deserialize);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
299
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
300
|
+
message: `Failed to get previous tracks for guild ${this.guildId}: ${err.message}`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
199
303
|
}
|
|
200
304
|
/**
|
|
201
305
|
* @returns The tracks in the queue from the start to the end.
|
|
202
306
|
*/
|
|
203
307
|
async getSlice(start = 0, end = -1) {
|
|
204
|
-
|
|
205
|
-
|
|
308
|
+
try {
|
|
309
|
+
const raw = await this.redis.lrange(this.queueKey, start, end === -1 ? -1 : end - 1);
|
|
310
|
+
return raw.map(this.deserialize);
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
314
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
315
|
+
message: `Failed to get slice of queue for guild ${this.guildId}: ${err.message}`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
206
318
|
}
|
|
207
319
|
/**
|
|
208
320
|
* @returns The tracks in the queue.
|
|
209
321
|
*/
|
|
210
322
|
async getTracks() {
|
|
211
|
-
|
|
212
|
-
|
|
323
|
+
try {
|
|
324
|
+
const raw = await this.redis.lrange(this.queueKey, 0, -1);
|
|
325
|
+
return raw.map(this.deserialize);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
329
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
330
|
+
message: `Failed to get tracks for guild ${this.guildId}: ${err.message}`,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
213
333
|
}
|
|
214
334
|
/**
|
|
215
335
|
* Maps the tracks in the queue.
|
|
@@ -227,104 +347,145 @@ class RedisQueue {
|
|
|
227
347
|
* @returns The removed tracks.
|
|
228
348
|
*/
|
|
229
349
|
async modifyAt(start, deleteCount = 0, ...items) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
350
|
+
try {
|
|
351
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
352
|
+
const removed = queue.splice(start, deleteCount, ...items.map(this.serialize));
|
|
353
|
+
await this.redis.del(this.queueKey);
|
|
354
|
+
if (queue.length > 0) {
|
|
355
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
356
|
+
}
|
|
357
|
+
return removed.map(this.deserialize);
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
361
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
362
|
+
message: `Failed to modify queue at index ${start} for guild ${this.guildId}: ${err.message}`,
|
|
363
|
+
});
|
|
235
364
|
}
|
|
236
|
-
return removed.map(this.deserialize);
|
|
237
365
|
}
|
|
238
366
|
/**
|
|
239
367
|
* Removes the newest track.
|
|
240
368
|
* @returns The newest track.
|
|
241
369
|
*/
|
|
242
370
|
async popPrevious() {
|
|
243
|
-
|
|
244
|
-
|
|
371
|
+
try {
|
|
372
|
+
// Pop the newest track from the TAIL
|
|
373
|
+
const raw = await this.redis.rpop(this.previousKey);
|
|
374
|
+
return raw ? this.deserialize(raw) : null;
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
378
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
379
|
+
message: `Failed to pop previous track for guild ${this.guildId}: ${err.message}`,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
245
382
|
}
|
|
246
383
|
async remove(startOrPos = 0, end) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
384
|
+
try {
|
|
385
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
386
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
387
|
+
let removed = [];
|
|
388
|
+
if (typeof end === "number") {
|
|
389
|
+
if (startOrPos >= end || startOrPos >= queue.length) {
|
|
390
|
+
throw new RangeError("Invalid range.");
|
|
391
|
+
}
|
|
392
|
+
removed = queue.slice(startOrPos, end);
|
|
393
|
+
queue.splice(startOrPos, end - startOrPos);
|
|
253
394
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
395
|
+
else {
|
|
396
|
+
removed = queue.splice(startOrPos, 1);
|
|
397
|
+
}
|
|
398
|
+
await this.redis.del(this.queueKey);
|
|
399
|
+
if (queue.length > 0) {
|
|
400
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
401
|
+
}
|
|
402
|
+
const deserialized = removed.map(this.deserialize);
|
|
403
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Removed ${removed.length} track(s) from position ${startOrPos}${end ? ` to ${end}` : ""}`);
|
|
404
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
405
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
406
|
+
details: {
|
|
407
|
+
type: "queue",
|
|
408
|
+
action: "remove",
|
|
409
|
+
tracks: deserialized,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
return deserialized;
|
|
259
413
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
414
|
+
catch (error) {
|
|
415
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
416
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
417
|
+
message: `Failed to remove track for guild ${this.guildId}: ${error.message}`,
|
|
418
|
+
});
|
|
263
419
|
}
|
|
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
420
|
}
|
|
276
421
|
/**
|
|
277
422
|
* Shuffles the queue round-robin style.
|
|
278
423
|
*/
|
|
279
424
|
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]];
|
|
425
|
+
try {
|
|
426
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
427
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
428
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
429
|
+
const userMap = new Map();
|
|
430
|
+
for (const track of deserialized) {
|
|
431
|
+
const userId = track.requester.id;
|
|
432
|
+
if (!userMap.has(userId))
|
|
433
|
+
userMap.set(userId, []);
|
|
434
|
+
userMap.get(userId).push(track);
|
|
295
435
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
436
|
+
// Shuffle each user's tracks
|
|
437
|
+
for (const tracks of userMap.values()) {
|
|
438
|
+
for (let i = tracks.length - 1; i > 0; i--) {
|
|
439
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
440
|
+
[tracks[i], tracks[j]] = [tracks[j], tracks[i]];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const users = [...userMap.keys()];
|
|
444
|
+
const queues = users.map((id) => userMap.get(id));
|
|
445
|
+
const shuffledQueue = [];
|
|
446
|
+
while (queues.some((q) => q.length > 0)) {
|
|
447
|
+
for (const q of queues) {
|
|
448
|
+
const track = q.shift();
|
|
449
|
+
if (track)
|
|
450
|
+
shuffledQueue.push(track);
|
|
451
|
+
}
|
|
305
452
|
}
|
|
453
|
+
await this.redis.del(this.queueKey);
|
|
454
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
455
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
456
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
457
|
+
details: {
|
|
458
|
+
type: "queue",
|
|
459
|
+
action: "roundRobin",
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
466
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
467
|
+
message: `Failed to roundRobinShuffle the queue for guild ${this.guildId}: ${error.message}`,
|
|
468
|
+
});
|
|
306
469
|
}
|
|
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
470
|
}
|
|
318
471
|
/**
|
|
319
472
|
* Sets the current track.
|
|
320
473
|
* @param track The track to set.
|
|
321
474
|
*/
|
|
322
475
|
async setCurrent(track) {
|
|
323
|
-
|
|
324
|
-
|
|
476
|
+
try {
|
|
477
|
+
if (track) {
|
|
478
|
+
await this.redis.set(this.currentKey, this.serialize(track));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
await this.redis.del(this.currentKey);
|
|
482
|
+
}
|
|
325
483
|
}
|
|
326
|
-
|
|
327
|
-
|
|
484
|
+
catch (error) {
|
|
485
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
486
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
487
|
+
message: `Failed to setCurrent the queue for guild ${this.guildId}: ${error.message}`,
|
|
488
|
+
});
|
|
328
489
|
}
|
|
329
490
|
}
|
|
330
491
|
/**
|
|
@@ -332,43 +493,67 @@ class RedisQueue {
|
|
|
332
493
|
* @param track The track to set.
|
|
333
494
|
*/
|
|
334
495
|
async setPrevious(track) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
496
|
+
try {
|
|
497
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
498
|
+
if (!tracks.length)
|
|
499
|
+
return;
|
|
500
|
+
await this.redis
|
|
501
|
+
.multi()
|
|
502
|
+
.del(this.previousKey)
|
|
503
|
+
.rpush(this.previousKey, ...tracks.map(this.serialize))
|
|
504
|
+
.exec();
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
508
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
509
|
+
message: `Failed to setPrevious the queue for guild ${this.guildId}: ${error.message}`,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
343
512
|
}
|
|
344
513
|
/**
|
|
345
514
|
* Shuffles the queue.
|
|
346
515
|
*/
|
|
347
516
|
async shuffle() {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
517
|
+
try {
|
|
518
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
519
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
520
|
+
for (let i = queue.length - 1; i > 0; i--) {
|
|
521
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
522
|
+
[queue[i], queue[j]] = [queue[j], queue[i]];
|
|
523
|
+
}
|
|
524
|
+
await this.redis.del(this.queueKey);
|
|
525
|
+
if (queue.length > 0) {
|
|
526
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
527
|
+
}
|
|
528
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
529
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
530
|
+
details: {
|
|
531
|
+
type: "queue",
|
|
532
|
+
action: "shuffle",
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] Shuffled the queue for: ${this.guildId}`);
|
|
353
536
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
537
|
+
catch (error) {
|
|
538
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
539
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
540
|
+
message: `Failed to shuffle the queue for guild ${this.guildId}: ${error.message}`,
|
|
541
|
+
});
|
|
357
542
|
}
|
|
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
543
|
}
|
|
367
544
|
/**
|
|
368
545
|
* @returns The size of the queue.
|
|
369
546
|
*/
|
|
370
547
|
async size() {
|
|
371
|
-
|
|
548
|
+
try {
|
|
549
|
+
return await this.redis.llen(this.queueKey);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
553
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
554
|
+
message: `Failed to get the size of the queue for guild ${this.guildId}: ${error.message}`,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
372
557
|
}
|
|
373
558
|
/**
|
|
374
559
|
* @returns Whether any tracks in the queue match the specified condition.
|
|
@@ -388,34 +573,42 @@ class RedisQueue {
|
|
|
388
573
|
* Shuffles the queue, but keeps the tracks of the same user together.
|
|
389
574
|
*/
|
|
390
575
|
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);
|
|
576
|
+
try {
|
|
577
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
578
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
579
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
580
|
+
const userMap = new Map();
|
|
581
|
+
for (const track of deserialized) {
|
|
582
|
+
const userId = track.requester.id;
|
|
583
|
+
if (!userMap.has(userId))
|
|
584
|
+
userMap.set(userId, []);
|
|
585
|
+
userMap.get(userId).push(track);
|
|
407
586
|
}
|
|
587
|
+
const shuffledQueue = [];
|
|
588
|
+
while (shuffledQueue.length < deserialized.length) {
|
|
589
|
+
for (const [, tracks] of userMap) {
|
|
590
|
+
const track = tracks.shift();
|
|
591
|
+
if (track)
|
|
592
|
+
shuffledQueue.push(track);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
await this.redis.del(this.queueKey);
|
|
596
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
597
|
+
this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
598
|
+
changeType: Enums_1.PlayerStateEventTypes.QueueChange,
|
|
599
|
+
details: {
|
|
600
|
+
type: "queue",
|
|
601
|
+
action: "userBlock",
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[QUEUE] userBlockShuffled the queue for: ${this.guildId}`);
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
throw new MagmastreamError_1.MagmaStreamError({
|
|
608
|
+
code: Enums_1.MagmaStreamErrorCode.QUEUE_REDIS_ERROR,
|
|
609
|
+
message: `Failed to userBlockShuffle the queue for guild ${this.guildId}: ${error.message}`,
|
|
610
|
+
});
|
|
408
611
|
}
|
|
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
612
|
}
|
|
420
613
|
// #endregion Public
|
|
421
614
|
// #region Private
|