magmastream 2.9.0-dev.2 → 2.9.0-dev.21

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.
@@ -6,14 +6,58 @@ const Manager_1 = require("./Manager"); // Import Manager to access emit method
6
6
  * The player's queue, the `current` property is the currently playing track, think of the rest as the up-coming tracks.
7
7
  */
8
8
  class Queue extends Array {
9
+ /** The current track */
10
+ current = null;
11
+ /** The previous tracks */
12
+ previous = [];
13
+ /** The Manager instance. */
14
+ manager;
15
+ /** The guild ID property. */
16
+ guildId;
17
+ /**
18
+ * Constructs a new Queue.
19
+ * @param guildId The guild ID.
20
+ * @param manager The Manager instance.
21
+ */
22
+ constructor(guildId, manager) {
23
+ super();
24
+ /** The Manager instance. */
25
+ this.manager = manager;
26
+ /** The guild property. */
27
+ this.guildId = guildId;
28
+ }
29
+ async getCurrent() {
30
+ return this.current;
31
+ }
32
+ async setCurrent(track) {
33
+ this.current = track;
34
+ }
35
+ async getPrevious() {
36
+ return [...this.previous];
37
+ }
38
+ async addPrevious(track) {
39
+ if (Array.isArray(track)) {
40
+ this.previous.unshift(...track);
41
+ }
42
+ else {
43
+ this.previous.unshift(track);
44
+ }
45
+ }
46
+ async setPrevious(tracks) {
47
+ this.previous = [...tracks];
48
+ }
49
+ async popPrevious() {
50
+ return this.previous.shift() || null; // get newest track
51
+ }
52
+ async clearPrevious() {
53
+ this.previous = [];
54
+ }
9
55
  /**
10
56
  * The total duration of the queue in milliseconds.
11
57
  * This includes the duration of the currently playing track.
12
58
  */
13
- get duration() {
14
- // Get the duration of the currently playing track, or 0 if there is none.
59
+ async duration() {
15
60
  const current = this.current?.duration ?? 0;
16
- // Return the sum of all durations in the queue including the current track.
17
61
  return this.reduce((acc, cur) => acc + (cur.duration || 0), current);
18
62
  }
19
63
  /**
@@ -21,7 +65,7 @@ class Queue extends Array {
21
65
  * This includes the current track if it is not null.
22
66
  * @returns The total size of tracks in the queue including the current track.
23
67
  */
24
- get totalSize() {
68
+ async totalSize() {
25
69
  return this.length + (this.current ? 1 : 0);
26
70
  }
27
71
  /**
@@ -29,41 +73,20 @@ class Queue extends Array {
29
73
  * This does not include the currently playing track.
30
74
  * @returns The size of tracks in the queue.
31
75
  */
32
- get size() {
76
+ async size() {
33
77
  return this.length;
34
78
  }
35
- /** The current track */
36
- current = null;
37
- /** The previous tracks */
38
- previous = [];
39
- /** The Manager instance. */
40
- manager;
41
- /** The guild ID property. */
42
- guildId;
43
- /**
44
- * Constructs a new Queue.
45
- * @param guildId The guild ID.
46
- * @param manager The Manager instance.
47
- */
48
- constructor(guildId, manager) {
49
- super();
50
- /** The Manager instance. */
51
- this.manager = manager;
52
- /** The guild property. */
53
- this.guildId = guildId;
54
- }
55
79
  /**
56
80
  * Adds a track to the queue.
57
81
  * @param track The track or tracks to add. Can be a single `Track` or an array of `Track`s.
58
82
  * @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.
59
83
  */
60
- add(track, offset) {
84
+ async add(track, offset) {
61
85
  // Get the track info as a string
62
86
  const trackInfo = Array.isArray(track) ? track.map((t) => JSON.stringify(t, null, 2)).join(", ") : JSON.stringify(track, null, 2);
63
87
  // Emit a debug message
64
88
  this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Added ${Array.isArray(track) ? track.length : 1} track(s) to queue: ${trackInfo}`);
65
89
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
66
- // If the track is valid, add it to the queue
67
90
  // If the queue is empty, set the track as the current track
68
91
  if (!this.current) {
69
92
  if (Array.isArray(track)) {
@@ -127,7 +150,7 @@ class Queue extends Array {
127
150
  },
128
151
  });
129
152
  }
130
- remove(startOrPosition = 0, end) {
153
+ async remove(startOrPosition = 0, end) {
131
154
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
132
155
  if (typeof end !== "undefined") {
133
156
  // Validate input for `start` and `end`
@@ -166,7 +189,7 @@ class Queue extends Array {
166
189
  * Clears the queue.
167
190
  * This will remove all tracks from the queue and emit a state update event.
168
191
  */
169
- clear() {
192
+ async clear() {
170
193
  // Capture the current state of the player for event emission.
171
194
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
172
195
  // Remove all items from the queue.
@@ -186,7 +209,7 @@ class Queue extends Array {
186
209
  * Shuffles the queue.
187
210
  * This will randomize the order of the tracks in the queue and emit a state update event.
188
211
  */
189
- shuffle() {
212
+ async shuffle() {
190
213
  // Capture the current state of the player for event emission.
191
214
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
192
215
  // Shuffle the queue.
@@ -207,7 +230,7 @@ class Queue extends Array {
207
230
  /**
208
231
  * Shuffles the queue to play tracks requested by each user one block at a time.
209
232
  */
210
- userBlockShuffle() {
233
+ async userBlockShuffle() {
211
234
  // Capture the current state of the player for event emission.
212
235
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
213
236
  // Group the tracks in the queue by the user that requested them.
@@ -247,8 +270,10 @@ class Queue extends Array {
247
270
  /**
248
271
  * Shuffles the queue to play tracks requested by each user one by one.
249
272
  */
250
- roundRobinShuffle() {
273
+ async roundRobinShuffle() {
274
+ // Capture the current state of the player for event emission.
251
275
  const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
276
+ // Group the tracks in the queue by the user that requested them.
252
277
  const userTracks = new Map();
253
278
  // Group the tracks in the queue by the user that requested them.
254
279
  this.forEach((track) => {
@@ -292,5 +317,40 @@ class Queue extends Array {
292
317
  // Emit a debug message indicating the queue has been shuffled for a specific guild ID.
293
318
  this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
294
319
  }
320
+ async dequeue() {
321
+ return super.shift();
322
+ }
323
+ async enqueueFront(track) {
324
+ if (Array.isArray(track)) {
325
+ this.unshift(...track);
326
+ }
327
+ else {
328
+ this.unshift(track);
329
+ }
330
+ }
331
+ async getTracks() {
332
+ return [...this]; // clone to avoid direct mutation
333
+ }
334
+ async getSlice(start, end) {
335
+ return this.slice(start, end); // Native sync method, still wrapped in a Promise
336
+ }
337
+ async modifyAt(start, deleteCount = 0, ...items) {
338
+ return super.splice(start, deleteCount, ...items);
339
+ }
340
+ async mapAsync(callback) {
341
+ return this.map(callback);
342
+ }
343
+ async filterAsync(callback) {
344
+ return this.filter(callback);
345
+ }
346
+ async findAsync(callback) {
347
+ return this.find(callback);
348
+ }
349
+ async someAsync(callback) {
350
+ return this.some(callback);
351
+ }
352
+ async everyAsync(callback) {
353
+ return this.every(callback);
354
+ }
295
355
  }
296
356
  exports.Queue = Queue;
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisQueue = void 0;
4
+ const Manager_1 = require("./Manager");
5
+ class RedisQueue {
6
+ guildId;
7
+ manager;
8
+ redis;
9
+ redisPrefix;
10
+ constructor(guildId, manager) {
11
+ this.guildId = guildId;
12
+ this.manager = manager;
13
+ this.redis = manager.redis;
14
+ this.redisPrefix = manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
15
+ ? manager.options.stateStorage.redisConfig.prefix
16
+ : `${manager.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
17
+ }
18
+ get queueKey() {
19
+ return `${this.redisPrefix}queue:${this.guildId}:tracks`;
20
+ }
21
+ get currentKey() {
22
+ return `${this.redisPrefix}queue:${this.guildId}:current`;
23
+ }
24
+ get previousKey() {
25
+ return `${this.redisPrefix}queue:${this.guildId}:previous`;
26
+ }
27
+ // Helper to serialize/deserialize Track
28
+ serialize(track) {
29
+ return JSON.stringify(track);
30
+ }
31
+ deserialize(data) {
32
+ return JSON.parse(data);
33
+ }
34
+ async getCurrent() {
35
+ const raw = await this.redis.get(this.currentKey);
36
+ return raw ? this.deserialize(raw) : null;
37
+ }
38
+ async setCurrent(track) {
39
+ if (track) {
40
+ await this.redis.set(this.currentKey, this.serialize(track));
41
+ }
42
+ else {
43
+ await this.redis.del(this.currentKey);
44
+ }
45
+ }
46
+ async getPrevious() {
47
+ const raw = await this.redis.lrange(this.previousKey, 0, -1);
48
+ return raw.map(this.deserialize);
49
+ }
50
+ async addPrevious(track) {
51
+ const tracks = Array.isArray(track) ? track : [track];
52
+ if (!tracks.length)
53
+ return;
54
+ const serialized = tracks.map(this.serialize);
55
+ if (!serialized.length)
56
+ return;
57
+ await this.redis.lpush(this.previousKey, ...serialized.reverse());
58
+ }
59
+ async setPrevious(track) {
60
+ const tracks = Array.isArray(track) ? track : [track];
61
+ if (!tracks.length)
62
+ return;
63
+ await this.redis.del(this.previousKey);
64
+ await this.redis.rpush(this.previousKey, ...tracks.map(this.serialize));
65
+ }
66
+ async popPrevious() {
67
+ const raw = await this.redis.lpop(this.previousKey); // get newest track (index 0)
68
+ return raw ? this.deserialize(raw) : null;
69
+ }
70
+ async clearPrevious() {
71
+ await this.redis.del(this.previousKey);
72
+ }
73
+ async add(track, offset) {
74
+ const isArray = Array.isArray(track);
75
+ const tracks = isArray ? track : [track];
76
+ const serialized = tracks.map((t) => this.serialize(t));
77
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
78
+ // If there's no current track, pop one from the list
79
+ if (!(await this.getCurrent())) {
80
+ const current = serialized.shift();
81
+ if (current) {
82
+ await this.setCurrent(this.deserialize(current));
83
+ }
84
+ }
85
+ if (typeof offset === "number" && !isNaN(offset)) {
86
+ const queue = await this.redis.lrange(this.queueKey, 0, -1);
87
+ queue.splice(offset, 0, ...serialized);
88
+ await this.redis.del(this.queueKey);
89
+ if (queue.length > 0) {
90
+ await this.redis.rpush(this.queueKey, ...queue);
91
+ }
92
+ }
93
+ else if (serialized.length > 0) {
94
+ await this.redis.rpush(this.queueKey, ...serialized);
95
+ }
96
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Added ${tracks.length} track(s) to queue`);
97
+ if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
98
+ if (!Array.isArray(track)) {
99
+ const botUser = (await this.manager.players.get(this.guildId).get("Internal_BotUser"));
100
+ if (botUser && botUser.id === track.requester.id) {
101
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
102
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
103
+ details: {
104
+ changeType: "autoPlayAdd",
105
+ tracks: Array.isArray(track) ? track : [track],
106
+ },
107
+ });
108
+ return;
109
+ }
110
+ }
111
+ }
112
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
113
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
114
+ details: {
115
+ changeType: "add",
116
+ tracks,
117
+ },
118
+ });
119
+ }
120
+ async remove(startOrPos = 0, end) {
121
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
122
+ const queue = await this.redis.lrange(this.queueKey, 0, -1);
123
+ let removed = [];
124
+ if (typeof end === "number") {
125
+ if (startOrPos >= end || startOrPos >= queue.length) {
126
+ throw new RangeError("Invalid range.");
127
+ }
128
+ removed = queue.slice(startOrPos, end);
129
+ queue.splice(startOrPos, end - startOrPos);
130
+ }
131
+ else {
132
+ removed = queue.splice(startOrPos, 1);
133
+ }
134
+ await this.redis.del(this.queueKey);
135
+ if (queue.length > 0) {
136
+ await this.redis.rpush(this.queueKey, ...queue);
137
+ }
138
+ const deserialized = removed.map(this.deserialize);
139
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Removed ${removed.length} track(s) from position ${startOrPos}${end ? ` to ${end}` : ""}`);
140
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
141
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
142
+ details: {
143
+ changeType: "remove",
144
+ tracks: deserialized,
145
+ },
146
+ });
147
+ return deserialized;
148
+ }
149
+ async clear() {
150
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
151
+ await this.redis.del(this.queueKey);
152
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
153
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
154
+ details: {
155
+ changeType: "clear",
156
+ tracks: [],
157
+ },
158
+ });
159
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Cleared the queue for: ${this.guildId}`);
160
+ }
161
+ async size() {
162
+ return await this.redis.llen(this.queueKey);
163
+ }
164
+ async totalSize() {
165
+ const size = await this.size();
166
+ return (await this.getCurrent()) ? size + 1 : size;
167
+ }
168
+ async duration() {
169
+ const tracks = await this.redis.lrange(this.queueKey, 0, -1);
170
+ const currentDuration = (await this.getCurrent())?.duration || 0;
171
+ const total = tracks.reduce((acc, raw) => {
172
+ try {
173
+ const parsed = this.deserialize(raw);
174
+ return acc + (parsed.duration || 0);
175
+ }
176
+ catch {
177
+ return acc;
178
+ }
179
+ }, currentDuration);
180
+ return total;
181
+ }
182
+ async shuffle() {
183
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
184
+ const queue = await this.redis.lrange(this.queueKey, 0, -1);
185
+ for (let i = queue.length - 1; i > 0; i--) {
186
+ const j = Math.floor(Math.random() * (i + 1));
187
+ [queue[i], queue[j]] = [queue[j], queue[i]];
188
+ }
189
+ await this.redis.del(this.queueKey);
190
+ if (queue.length > 0) {
191
+ await this.redis.rpush(this.queueKey, ...queue);
192
+ }
193
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
194
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
195
+ details: { changeType: "shuffle" },
196
+ });
197
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Shuffled the queue for: ${this.guildId}`);
198
+ }
199
+ async userBlockShuffle() {
200
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
201
+ const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
202
+ const deserialized = rawTracks.map(this.deserialize);
203
+ const userMap = new Map();
204
+ for (const track of deserialized) {
205
+ const userId = track.requester.id;
206
+ if (!userMap.has(userId))
207
+ userMap.set(userId, []);
208
+ userMap.get(userId).push(track);
209
+ }
210
+ const shuffledQueue = [];
211
+ while (shuffledQueue.length < deserialized.length) {
212
+ for (const [, tracks] of userMap) {
213
+ const track = tracks.shift();
214
+ if (track)
215
+ shuffledQueue.push(track);
216
+ }
217
+ }
218
+ await this.redis.del(this.queueKey);
219
+ await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
220
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
221
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
222
+ details: { changeType: "userBlock" },
223
+ });
224
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] userBlockShuffled the queue for: ${this.guildId}`);
225
+ }
226
+ async roundRobinShuffle() {
227
+ const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
228
+ const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
229
+ const deserialized = rawTracks.map(this.deserialize);
230
+ const userMap = new Map();
231
+ for (const track of deserialized) {
232
+ const userId = track.requester.id;
233
+ if (!userMap.has(userId))
234
+ userMap.set(userId, []);
235
+ userMap.get(userId).push(track);
236
+ }
237
+ // Shuffle each user's tracks
238
+ for (const tracks of userMap.values()) {
239
+ for (let i = tracks.length - 1; i > 0; i--) {
240
+ const j = Math.floor(Math.random() * (i + 1));
241
+ [tracks[i], tracks[j]] = [tracks[j], tracks[i]];
242
+ }
243
+ }
244
+ const users = [...userMap.keys()];
245
+ const queues = users.map((id) => userMap.get(id));
246
+ const shuffledQueue = [];
247
+ while (queues.some((q) => q.length > 0)) {
248
+ for (const q of queues) {
249
+ const track = q.shift();
250
+ if (track)
251
+ shuffledQueue.push(track);
252
+ }
253
+ }
254
+ await this.redis.del(this.queueKey);
255
+ await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
256
+ this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
257
+ changeType: Manager_1.PlayerStateEventTypes.QueueChange,
258
+ details: { changeType: "roundRobin" },
259
+ });
260
+ this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
261
+ }
262
+ async dequeue() {
263
+ const raw = await this.redis.lpop(this.queueKey);
264
+ return raw ? this.deserialize(raw) : undefined;
265
+ }
266
+ async enqueueFront(track) {
267
+ const serialized = Array.isArray(track) ? track.map(this.serialize) : [this.serialize(track)];
268
+ // Redis: LPUSH adds to front, reverse to maintain order if multiple tracks
269
+ await this.redis.lpush(this.queueKey, ...serialized.reverse());
270
+ }
271
+ async getTracks() {
272
+ const raw = await this.redis.lrange(this.queueKey, 0, -1);
273
+ return raw.map(this.deserialize);
274
+ }
275
+ async getSlice(start = 0, end = -1) {
276
+ const raw = await this.redis.lrange(this.queueKey, start, end === -1 ? -1 : end - 1);
277
+ return raw.map(this.deserialize);
278
+ }
279
+ async modifyAt(start, deleteCount = 0, ...items) {
280
+ const queue = await this.redis.lrange(this.queueKey, 0, -1);
281
+ const removed = queue.splice(start, deleteCount, ...items.map(this.serialize));
282
+ await this.redis.del(this.queueKey);
283
+ if (queue.length > 0) {
284
+ await this.redis.rpush(this.queueKey, ...queue);
285
+ }
286
+ return removed.map(this.deserialize);
287
+ }
288
+ async mapAsync(callback) {
289
+ const tracks = await this.getTracks(); // same as lrange + deserialize
290
+ return tracks.map(callback);
291
+ }
292
+ async filterAsync(callback) {
293
+ const tracks = await this.getTracks();
294
+ return tracks.filter(callback);
295
+ }
296
+ async findAsync(callback) {
297
+ const tracks = await this.getTracks();
298
+ return tracks.find(callback);
299
+ }
300
+ async someAsync(callback) {
301
+ const tracks = await this.getTracks();
302
+ return tracks.some(callback);
303
+ }
304
+ async everyAsync(callback) {
305
+ const tracks = await this.getTracks();
306
+ return tracks.every(callback);
307
+ }
308
+ }
309
+ exports.RedisQueue = RedisQueue;