magmastream 2.9.0-dev.1 → 2.9.0-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 +155 -45
- package/dist/storage/CollectionPlayerStore.js +80 -0
- package/dist/storage/RedisPlayerStore.js +159 -0
- package/dist/structures/Manager.js +59 -21
- package/dist/structures/Node.js +46 -37
- package/dist/structures/Player.js +45 -42
- package/dist/structures/Queue.js +100 -47
- package/dist/structures/RedisQueue.js +301 -0
- package/dist/structures/Utils.js +345 -344
- package/dist/utils/logExecutionTime.js +11 -0
- package/dist/utils/managerCheck.js +8 -5
- package/package.json +6 -5
|
@@ -0,0 +1,301 @@
|
|
|
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
|
+
current = null;
|
|
9
|
+
previous = [];
|
|
10
|
+
redis;
|
|
11
|
+
redisPrefix;
|
|
12
|
+
constructor(guildId, manager) {
|
|
13
|
+
this.guildId = guildId;
|
|
14
|
+
this.manager = manager;
|
|
15
|
+
this.redis = manager.redis;
|
|
16
|
+
this.redisPrefix = manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
|
|
17
|
+
? manager.options.stateStorage.redisConfig.prefix
|
|
18
|
+
: `${manager.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
|
|
19
|
+
}
|
|
20
|
+
get queueKey() {
|
|
21
|
+
return `${this.redisPrefix}queue:${this.guildId}:tracks`;
|
|
22
|
+
}
|
|
23
|
+
get currentKey() {
|
|
24
|
+
return `${this.redisPrefix}queue:${this.guildId}:current`;
|
|
25
|
+
}
|
|
26
|
+
get previousKey() {
|
|
27
|
+
return `${this.redisPrefix}queue:${this.guildId}:previous`;
|
|
28
|
+
}
|
|
29
|
+
// Helper to serialize/deserialize Track
|
|
30
|
+
serialize(track) {
|
|
31
|
+
return JSON.stringify(track);
|
|
32
|
+
}
|
|
33
|
+
deserialize(data) {
|
|
34
|
+
return JSON.parse(data);
|
|
35
|
+
}
|
|
36
|
+
async getCurrent() {
|
|
37
|
+
const raw = await this.redis.get(this.currentKey);
|
|
38
|
+
return raw ? this.deserialize(raw) : null;
|
|
39
|
+
}
|
|
40
|
+
async setCurrent(track) {
|
|
41
|
+
if (track) {
|
|
42
|
+
await this.redis.set(this.currentKey, this.serialize(track));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await this.redis.del(this.currentKey);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async getPrevious() {
|
|
49
|
+
const raw = await this.redis.lrange(this.previousKey, 0, -1);
|
|
50
|
+
return raw.map(this.deserialize);
|
|
51
|
+
}
|
|
52
|
+
async addPrevious(track) {
|
|
53
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
54
|
+
if (!tracks.length)
|
|
55
|
+
return;
|
|
56
|
+
const serialized = tracks.map(this.serialize);
|
|
57
|
+
if (!serialized.length)
|
|
58
|
+
return; // avoid lpush with no values
|
|
59
|
+
await this.redis.lpush(this.previousKey, ...serialized.reverse());
|
|
60
|
+
}
|
|
61
|
+
async clearPrevious() {
|
|
62
|
+
await this.redis.del(this.previousKey);
|
|
63
|
+
}
|
|
64
|
+
async add(track, offset) {
|
|
65
|
+
const isArray = Array.isArray(track);
|
|
66
|
+
const tracks = isArray ? track : [track];
|
|
67
|
+
const serialized = tracks.map((t) => this.serialize(t));
|
|
68
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
69
|
+
// If there's no current track, pop one from the list
|
|
70
|
+
if (!this.current) {
|
|
71
|
+
const current = serialized.shift();
|
|
72
|
+
if (current) {
|
|
73
|
+
await this.redis.set(this.currentKey, current);
|
|
74
|
+
this.current = this.deserialize(current);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (typeof offset === "number" && !isNaN(offset)) {
|
|
78
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
79
|
+
queue.splice(offset, 0, ...serialized);
|
|
80
|
+
await this.redis.del(this.queueKey);
|
|
81
|
+
if (queue.length > 0) {
|
|
82
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (serialized.length > 0) {
|
|
86
|
+
await this.redis.rpush(this.queueKey, ...serialized);
|
|
87
|
+
}
|
|
88
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Added ${tracks.length} track(s) to queue`);
|
|
89
|
+
if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
|
|
90
|
+
if (!Array.isArray(track)) {
|
|
91
|
+
const botUser = (await this.manager.players.get(this.guildId).get("Internal_BotUser"));
|
|
92
|
+
if (botUser && botUser.id === track.requester.id) {
|
|
93
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
94
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
95
|
+
details: {
|
|
96
|
+
changeType: "autoPlayAdd",
|
|
97
|
+
tracks: Array.isArray(track) ? track : [track],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
105
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
106
|
+
details: {
|
|
107
|
+
changeType: "add",
|
|
108
|
+
tracks,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async remove(startOrPos = 0, end) {
|
|
113
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
114
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
115
|
+
let removed = [];
|
|
116
|
+
if (typeof end === "number") {
|
|
117
|
+
if (startOrPos >= end || startOrPos >= queue.length) {
|
|
118
|
+
throw new RangeError("Invalid range.");
|
|
119
|
+
}
|
|
120
|
+
removed = queue.slice(startOrPos, end);
|
|
121
|
+
queue.splice(startOrPos, end - startOrPos);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
removed = queue.splice(startOrPos, 1);
|
|
125
|
+
}
|
|
126
|
+
await this.redis.del(this.queueKey);
|
|
127
|
+
if (queue.length > 0) {
|
|
128
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
129
|
+
}
|
|
130
|
+
const deserialized = removed.map(this.deserialize);
|
|
131
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Removed ${removed.length} track(s) from position ${startOrPos}${end ? ` to ${end}` : ""}`);
|
|
132
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
133
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
134
|
+
details: {
|
|
135
|
+
changeType: "remove",
|
|
136
|
+
tracks: deserialized,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return deserialized;
|
|
140
|
+
}
|
|
141
|
+
async clear() {
|
|
142
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
143
|
+
await this.redis.del(this.queueKey);
|
|
144
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
145
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
146
|
+
details: {
|
|
147
|
+
changeType: "clear",
|
|
148
|
+
tracks: [],
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Cleared the queue for: ${this.guildId}`);
|
|
152
|
+
}
|
|
153
|
+
async size() {
|
|
154
|
+
return await this.redis.llen(this.queueKey);
|
|
155
|
+
}
|
|
156
|
+
async totalSize() {
|
|
157
|
+
const size = await this.size();
|
|
158
|
+
return this.current ? size + 1 : size;
|
|
159
|
+
}
|
|
160
|
+
async duration() {
|
|
161
|
+
const tracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
162
|
+
const currentDuration = this.current?.duration || 0;
|
|
163
|
+
const total = tracks.reduce((acc, raw) => {
|
|
164
|
+
try {
|
|
165
|
+
const parsed = this.deserialize(raw);
|
|
166
|
+
return acc + (parsed.duration || 0);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return acc;
|
|
170
|
+
}
|
|
171
|
+
}, currentDuration);
|
|
172
|
+
return total;
|
|
173
|
+
}
|
|
174
|
+
async shuffle() {
|
|
175
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
176
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
177
|
+
for (let i = queue.length - 1; i > 0; i--) {
|
|
178
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
179
|
+
[queue[i], queue[j]] = [queue[j], queue[i]];
|
|
180
|
+
}
|
|
181
|
+
await this.redis.del(this.queueKey);
|
|
182
|
+
if (queue.length > 0) {
|
|
183
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
184
|
+
}
|
|
185
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
186
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
187
|
+
details: { changeType: "shuffle" },
|
|
188
|
+
});
|
|
189
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Shuffled the queue for: ${this.guildId}`);
|
|
190
|
+
}
|
|
191
|
+
async userBlockShuffle() {
|
|
192
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
193
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
194
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
195
|
+
const userMap = new Map();
|
|
196
|
+
for (const track of deserialized) {
|
|
197
|
+
const userId = track.requester.id;
|
|
198
|
+
if (!userMap.has(userId))
|
|
199
|
+
userMap.set(userId, []);
|
|
200
|
+
userMap.get(userId).push(track);
|
|
201
|
+
}
|
|
202
|
+
const shuffledQueue = [];
|
|
203
|
+
while (shuffledQueue.length < deserialized.length) {
|
|
204
|
+
for (const [, tracks] of userMap) {
|
|
205
|
+
const track = tracks.shift();
|
|
206
|
+
if (track)
|
|
207
|
+
shuffledQueue.push(track);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
await this.redis.del(this.queueKey);
|
|
211
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
212
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
213
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
214
|
+
details: { changeType: "userBlock" },
|
|
215
|
+
});
|
|
216
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] userBlockShuffled the queue for: ${this.guildId}`);
|
|
217
|
+
}
|
|
218
|
+
async roundRobinShuffle() {
|
|
219
|
+
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
220
|
+
const rawTracks = await this.redis.lrange(this.queueKey, 0, -1);
|
|
221
|
+
const deserialized = rawTracks.map(this.deserialize);
|
|
222
|
+
const userMap = new Map();
|
|
223
|
+
for (const track of deserialized) {
|
|
224
|
+
const userId = track.requester.id;
|
|
225
|
+
if (!userMap.has(userId))
|
|
226
|
+
userMap.set(userId, []);
|
|
227
|
+
userMap.get(userId).push(track);
|
|
228
|
+
}
|
|
229
|
+
// Shuffle each user's tracks
|
|
230
|
+
for (const tracks of userMap.values()) {
|
|
231
|
+
for (let i = tracks.length - 1; i > 0; i--) {
|
|
232
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
233
|
+
[tracks[i], tracks[j]] = [tracks[j], tracks[i]];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const users = [...userMap.keys()];
|
|
237
|
+
const queues = users.map((id) => userMap.get(id));
|
|
238
|
+
const shuffledQueue = [];
|
|
239
|
+
while (queues.some((q) => q.length > 0)) {
|
|
240
|
+
for (const q of queues) {
|
|
241
|
+
const track = q.shift();
|
|
242
|
+
if (track)
|
|
243
|
+
shuffledQueue.push(track);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
await this.redis.del(this.queueKey);
|
|
247
|
+
await this.redis.rpush(this.queueKey, ...shuffledQueue.map(this.serialize));
|
|
248
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
249
|
+
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
250
|
+
details: { changeType: "roundRobin" },
|
|
251
|
+
});
|
|
252
|
+
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
|
|
253
|
+
}
|
|
254
|
+
async dequeue() {
|
|
255
|
+
const raw = await this.redis.lpop(this.queueKey);
|
|
256
|
+
return raw ? this.deserialize(raw) : undefined;
|
|
257
|
+
}
|
|
258
|
+
async enqueueFront(track) {
|
|
259
|
+
const serialized = Array.isArray(track) ? track.map(this.serialize) : [this.serialize(track)];
|
|
260
|
+
// Redis: LPUSH adds to front, reverse to maintain order if multiple tracks
|
|
261
|
+
await this.redis.lpush(this.queueKey, ...serialized.reverse());
|
|
262
|
+
}
|
|
263
|
+
async getTracks() {
|
|
264
|
+
const raw = await this.redis.lrange(this.queueKey, 0, -1);
|
|
265
|
+
return raw.map(this.deserialize);
|
|
266
|
+
}
|
|
267
|
+
async getSlice(start = 0, end = -1) {
|
|
268
|
+
const raw = await this.redis.lrange(this.queueKey, start, end === -1 ? -1 : end - 1);
|
|
269
|
+
return raw.map(this.deserialize);
|
|
270
|
+
}
|
|
271
|
+
async modifyAt(start, deleteCount = 0, ...items) {
|
|
272
|
+
const queue = await this.redis.lrange(this.queueKey, 0, -1);
|
|
273
|
+
const removed = queue.splice(start, deleteCount, ...items.map(this.serialize));
|
|
274
|
+
await this.redis.del(this.queueKey);
|
|
275
|
+
if (queue.length > 0) {
|
|
276
|
+
await this.redis.rpush(this.queueKey, ...queue);
|
|
277
|
+
}
|
|
278
|
+
return removed.map(this.deserialize);
|
|
279
|
+
}
|
|
280
|
+
async mapAsync(callback) {
|
|
281
|
+
const tracks = await this.getTracks(); // same as lrange + deserialize
|
|
282
|
+
return tracks.map(callback);
|
|
283
|
+
}
|
|
284
|
+
async filterAsync(callback) {
|
|
285
|
+
const tracks = await this.getTracks();
|
|
286
|
+
return tracks.filter(callback);
|
|
287
|
+
}
|
|
288
|
+
async findAsync(callback) {
|
|
289
|
+
const tracks = await this.getTracks();
|
|
290
|
+
return tracks.find(callback);
|
|
291
|
+
}
|
|
292
|
+
async someAsync(callback) {
|
|
293
|
+
const tracks = await this.getTracks();
|
|
294
|
+
return tracks.some(callback);
|
|
295
|
+
}
|
|
296
|
+
async everyAsync(callback) {
|
|
297
|
+
const tracks = await this.getTracks();
|
|
298
|
+
return tracks.every(callback);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.RedisQueue = RedisQueue;
|