ziplayer 0.2.6 → 0.2.7-dev.1
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/AI-Guide.md +607 -0
- package/README.md +513 -196
- package/dist/extensions/BaseExtension.d.ts +1 -0
- package/dist/extensions/BaseExtension.d.ts.map +1 -1
- package/dist/extensions/BaseExtension.js.map +1 -1
- package/dist/extensions/index.d.ts +38 -3
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +259 -41
- package/dist/extensions/index.js.map +1 -1
- package/dist/persistence/PersistenceManager.d.ts +61 -0
- package/dist/persistence/PersistenceManager.d.ts.map +1 -0
- package/dist/persistence/PersistenceManager.js +551 -0
- package/dist/persistence/PersistenceManager.js.map +1 -0
- package/dist/plugins/BasePlugin.js +1 -1
- package/dist/plugins/BasePlugin.js.map +1 -1
- package/dist/plugins/index.d.ts +19 -4
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +273 -146
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/FilterManager.js +3 -3
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +64 -14
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +344 -91
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +125 -91
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +406 -111
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/structures/Queue.d.ts +136 -31
- package/dist/structures/Queue.d.ts.map +1 -1
- package/dist/structures/Queue.js +265 -46
- package/dist/structures/Queue.js.map +1 -1
- package/dist/types/index.d.ts +39 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +55 -0
- package/dist/types/persistence.d.ts.map +1 -0
- package/dist/types/persistence.js +3 -0
- package/dist/types/persistence.js.map +1 -0
- package/package.json +47 -46
- package/src/extensions/BaseExtension.ts +36 -35
- package/src/extensions/index.ts +473 -190
- package/src/index.ts +16 -16
- package/src/persistence/PersistenceManager.ts +572 -0
- package/src/plugins/BasePlugin.ts +27 -27
- package/src/plugins/index.ts +403 -236
- package/src/structures/FilterManager.ts +303 -303
- package/src/structures/Player.ts +1962 -1689
- package/src/structures/PlayerManager.ts +788 -416
- package/src/structures/Queue.ts +599 -354
- package/src/types/index.ts +406 -373
- package/src/types/persistence.ts +65 -0
- package/src/types/plugin.ts +1 -1
- package/src/utils/timeout.ts +10 -10
- package/tsconfig.json +22 -23
|
@@ -5,6 +5,7 @@ exports.getInstance = getInstance;
|
|
|
5
5
|
const events_1 = require("events");
|
|
6
6
|
const Player_1 = require("./Player");
|
|
7
7
|
const timeout_1 = require("../utils/timeout");
|
|
8
|
+
const PersistenceManager_1 = require("../persistence/PersistenceManager");
|
|
8
9
|
const GLOBAL_MANAGER_KEY = Symbol.for("ziplayer.PlayerManager.instance");
|
|
9
10
|
const getGlobalManager = () => {
|
|
10
11
|
try {
|
|
@@ -46,7 +47,9 @@ const setGlobalManager = (instance) => {
|
|
|
46
47
|
* nodes: [{ host: "localhost", port: 2333, password: "youshallnotpass" }]
|
|
47
48
|
* })
|
|
48
49
|
* ],
|
|
49
|
-
* extractorTimeout: 10000
|
|
50
|
+
* extractorTimeout: 10000,
|
|
51
|
+
* autoCleanup: true,
|
|
52
|
+
* cleanupInterval: 60000
|
|
50
53
|
* });
|
|
51
54
|
*
|
|
52
55
|
* // Create a player for a guild
|
|
@@ -72,7 +75,7 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
72
75
|
}
|
|
73
76
|
debug(message, ...optionalParams) {
|
|
74
77
|
if (this.listenerCount("debug") > 0) {
|
|
75
|
-
this.emit("debug", message
|
|
78
|
+
this.emit("debug", `[PlayerManager] ${message}`, ...optionalParams);
|
|
76
79
|
if (!this.B_debug) {
|
|
77
80
|
this.B_debug = true;
|
|
78
81
|
}
|
|
@@ -81,9 +84,18 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
81
84
|
constructor(options = {}) {
|
|
82
85
|
super();
|
|
83
86
|
this.players = new Map();
|
|
87
|
+
this.SEARCH_CACHE_TTL = 60 * 1000; // 1 minute
|
|
88
|
+
this.MAX_CACHE_SIZE = 100;
|
|
89
|
+
this.cleanupInterval = null;
|
|
90
|
+
this.statsInterval = null;
|
|
84
91
|
this.B_debug = false;
|
|
85
92
|
this.extractorTimeout = 10000;
|
|
93
|
+
this.autoCleanup = true;
|
|
94
|
+
this.cleanupTimeout = 60000; // 1 minute
|
|
95
|
+
this.enableSearchCache = true;
|
|
86
96
|
this.plugins = [];
|
|
97
|
+
this.searchCache = new Map();
|
|
98
|
+
// Initialize plugins
|
|
87
99
|
const provided = options.plugins || [];
|
|
88
100
|
for (const p of provided) {
|
|
89
101
|
try {
|
|
@@ -94,13 +106,30 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
94
106
|
const instance = new p();
|
|
95
107
|
this.plugins.push(instance);
|
|
96
108
|
}
|
|
109
|
+
this.debug(`Registered plugin: ${p.name || "unnamed"}`);
|
|
97
110
|
}
|
|
98
111
|
catch (e) {
|
|
99
|
-
this.debug(`
|
|
112
|
+
this.debug(`Failed to init plugin:`, e);
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
this.extensions = options.extensions || [];
|
|
116
|
+
this.extractorTimeout = options.extractorTimeout ?? 10000;
|
|
117
|
+
this.autoCleanup = options.autoCleanup ?? true;
|
|
118
|
+
this.cleanupTimeout = options.cleanupInterval ?? 60000;
|
|
119
|
+
this.enableSearchCache = options.enableSearchCache ?? true;
|
|
120
|
+
if (options.persistence) {
|
|
121
|
+
this.initPersistence(options.persistence);
|
|
122
|
+
}
|
|
123
|
+
// Setup auto cleanup
|
|
124
|
+
if (this.autoCleanup) {
|
|
125
|
+
this.startAutoCleanup();
|
|
126
|
+
}
|
|
127
|
+
// Setup stats collection (optional)
|
|
128
|
+
if (options.enableStatsCollection) {
|
|
129
|
+
this.startStatsCollection();
|
|
130
|
+
}
|
|
103
131
|
setGlobalManager(this);
|
|
132
|
+
this.debug(`Initialized with ${this.plugins.length} plugins, ${this.extensions.length} extensions`);
|
|
104
133
|
}
|
|
105
134
|
withTimeout(promise, message) {
|
|
106
135
|
const timeout = this.extractorTimeout;
|
|
@@ -113,48 +142,108 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
113
142
|
return guildOrId.id;
|
|
114
143
|
throw new Error("Invalid guild or guildId provided.");
|
|
115
144
|
}
|
|
145
|
+
getSearchCacheKey(query) {
|
|
146
|
+
return query.toLowerCase().trim();
|
|
147
|
+
}
|
|
148
|
+
getCachedSearch(query) {
|
|
149
|
+
if (!this.enableSearchCache)
|
|
150
|
+
return null;
|
|
151
|
+
const key = this.getSearchCacheKey(query);
|
|
152
|
+
const cached = this.searchCache.get(key);
|
|
153
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
154
|
+
this.debug(`[Cache] Search hit for: ${query}`);
|
|
155
|
+
return cached.data;
|
|
156
|
+
}
|
|
157
|
+
if (cached) {
|
|
158
|
+
this.searchCache.delete(key);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
setCachedSearch(query, result) {
|
|
163
|
+
if (!this.enableSearchCache)
|
|
164
|
+
return;
|
|
165
|
+
// Clean up old entries if cache is too large
|
|
166
|
+
if (this.searchCache.size >= this.MAX_CACHE_SIZE) {
|
|
167
|
+
const oldest = Array.from(this.searchCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
|
|
168
|
+
if (oldest)
|
|
169
|
+
this.searchCache.delete(oldest[0]);
|
|
170
|
+
}
|
|
171
|
+
const key = this.getSearchCacheKey(query);
|
|
172
|
+
this.searchCache.set(key, {
|
|
173
|
+
data: result,
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
expiresAt: Date.now() + this.SEARCH_CACHE_TTL,
|
|
176
|
+
});
|
|
177
|
+
this.debug(`[Cache] Search stored for: ${query}`);
|
|
178
|
+
}
|
|
179
|
+
clearExpiredCache() {
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
let expiredCount = 0;
|
|
182
|
+
for (const [key, entry] of this.searchCache) {
|
|
183
|
+
if (now >= entry.expiresAt) {
|
|
184
|
+
this.searchCache.delete(key);
|
|
185
|
+
expiredCount++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (expiredCount > 0) {
|
|
189
|
+
this.debug(`[Cache] Cleared ${expiredCount} expired search entries`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
startAutoCleanup() {
|
|
193
|
+
if (this.cleanupInterval) {
|
|
194
|
+
clearInterval(this.cleanupInterval);
|
|
195
|
+
}
|
|
196
|
+
this.cleanupInterval = setInterval(() => {
|
|
197
|
+
this.cleanupInactivePlayers();
|
|
198
|
+
this.clearExpiredCache();
|
|
199
|
+
}, this.cleanupTimeout);
|
|
200
|
+
this.debug(`Auto-cleanup started with interval: ${this.cleanupTimeout}ms`);
|
|
201
|
+
}
|
|
202
|
+
startStatsCollection() {
|
|
203
|
+
if (this.statsInterval) {
|
|
204
|
+
clearInterval(this.statsInterval);
|
|
205
|
+
}
|
|
206
|
+
this.statsInterval = setInterval(() => {
|
|
207
|
+
const stats = this.getStats();
|
|
208
|
+
this.emit("stats", stats);
|
|
209
|
+
}, 30000); // Every 30 seconds
|
|
210
|
+
}
|
|
211
|
+
cleanupInactivePlayers() {
|
|
212
|
+
let cleanedCount = 0;
|
|
213
|
+
for (const [guildId, player] of this.players) {
|
|
214
|
+
// Clean up players that are not playing and not connected
|
|
215
|
+
if (!player.isPlaying && !player.connection && player.queue.isEmpty) {
|
|
216
|
+
const idleTime = Date.now() - player._lastActivity || Date.now();
|
|
217
|
+
if (idleTime > this.cleanupTimeout) {
|
|
218
|
+
this.debug(`Cleaning up inactive player for guild: ${guildId}`);
|
|
219
|
+
player.destroy();
|
|
220
|
+
this.players.delete(guildId);
|
|
221
|
+
cleanedCount++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (cleanedCount > 0) {
|
|
226
|
+
this.debug(`Cleaned up ${cleanedCount} inactive players`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
116
229
|
/**
|
|
117
230
|
* Create a new player for a guild
|
|
118
231
|
*
|
|
119
232
|
* @param {string | {id: string}} guildOrId - Guild ID or guild object
|
|
120
233
|
* @param {PlayerOptions} options - Player configuration options
|
|
121
234
|
* @returns {Promise<Player>} The created player instance
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* // Create player with basic options
|
|
125
|
-
* const player = await manager.create(guildId, {
|
|
126
|
-
* tts: { interrupt: true, volume: 1 },
|
|
127
|
-
* leaveOnEnd: true,
|
|
128
|
-
* leaveTimeout: 30000
|
|
129
|
-
* });
|
|
130
|
-
*
|
|
131
|
-
* // Create player with advanced options
|
|
132
|
-
* const advancedPlayer = await manager.create(guild, {
|
|
133
|
-
* volume: 0.8,
|
|
134
|
-
* quality: "high",
|
|
135
|
-
* selfDeaf: false,
|
|
136
|
-
* selfMute: false,
|
|
137
|
-
* tts: {
|
|
138
|
-
* createPlayer: true,
|
|
139
|
-
* interrupt: true,
|
|
140
|
-
* volume: 1.0,
|
|
141
|
-
* Max_Time_TTS: 30000
|
|
142
|
-
* },
|
|
143
|
-
* userdata: { customData: "example" }
|
|
144
|
-
* });
|
|
145
|
-
*
|
|
146
|
-
* // Connect and play immediately
|
|
147
|
-
* await player.connect(voiceChannel);
|
|
148
|
-
* await player.play("Never Gonna Give You Up", userId);
|
|
149
235
|
*/
|
|
150
236
|
async create(guildOrId, options) {
|
|
151
237
|
const guildId = this.resolveGuildId(guildOrId);
|
|
152
238
|
if (this.players.has(guildId)) {
|
|
239
|
+
this.debug(`Player already exists for guildId: ${guildId}, returning existing`);
|
|
153
240
|
return this.players.get(guildId);
|
|
154
241
|
}
|
|
155
|
-
this.debug(`
|
|
242
|
+
this.debug(`Creating player for guildId: ${guildId}`);
|
|
156
243
|
const player = new Player_1.Player(guildId, options, this);
|
|
244
|
+
// Add all registered plugins
|
|
157
245
|
this.plugins.forEach((plugin) => player.addPlugin(plugin));
|
|
246
|
+
// Activate extensions
|
|
158
247
|
let extsToActivate = [];
|
|
159
248
|
const optExts = options?.extensions;
|
|
160
249
|
if (Array.isArray(optExts)) {
|
|
@@ -172,6 +261,10 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
172
261
|
extsToActivate = optExts;
|
|
173
262
|
}
|
|
174
263
|
}
|
|
264
|
+
else {
|
|
265
|
+
// Use all extensions by default
|
|
266
|
+
extsToActivate = this.extensions;
|
|
267
|
+
}
|
|
175
268
|
for (const ext of extsToActivate) {
|
|
176
269
|
let instance = ext;
|
|
177
270
|
if (typeof ext === "function") {
|
|
@@ -179,7 +272,7 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
179
272
|
instance = new ext(player);
|
|
180
273
|
}
|
|
181
274
|
catch (e) {
|
|
182
|
-
this.debug(`
|
|
275
|
+
this.debug(`Extension constructor error for ${ext.name}:`, e);
|
|
183
276
|
continue;
|
|
184
277
|
}
|
|
185
278
|
}
|
|
@@ -192,11 +285,11 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
192
285
|
let activated = true;
|
|
193
286
|
try {
|
|
194
287
|
activated = await (0, timeout_1.withTimeout)(Promise.resolve(extInstance.active({ manager: this, player })), player.options.extractorTimeout ?? 15000, `Extension ${extInstance?.name} activation timed out`);
|
|
195
|
-
this.debug(`
|
|
288
|
+
this.debug(`Extension ${extInstance?.name} active check returned: ${activated}`);
|
|
196
289
|
}
|
|
197
290
|
catch (e) {
|
|
198
291
|
activated = false;
|
|
199
|
-
this.debug(`
|
|
292
|
+
this.debug(`Extension activation error for ${extInstance?.name}:`, e);
|
|
200
293
|
}
|
|
201
294
|
if (activated === false) {
|
|
202
295
|
player.detachExtension(extInstance);
|
|
@@ -205,14 +298,25 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
205
298
|
}
|
|
206
299
|
}
|
|
207
300
|
}
|
|
208
|
-
// Forward all player events
|
|
301
|
+
// Forward all player events to manager
|
|
302
|
+
this.setupEventForwarding(player, guildId);
|
|
303
|
+
// Mark last activity
|
|
304
|
+
player._lastActivity = Date.now();
|
|
305
|
+
this.players.set(guildId, player);
|
|
306
|
+
this.debug(`Player created for guildId: ${guildId}`);
|
|
307
|
+
return player;
|
|
308
|
+
}
|
|
309
|
+
setupEventForwarding(player, guildId) {
|
|
209
310
|
player.on("willPlay", (track, tracks) => this.emit("willPlay", player, track, tracks));
|
|
210
|
-
player.on("trackStart", (track) =>
|
|
311
|
+
player.on("trackStart", (track) => {
|
|
312
|
+
player._lastActivity = Date.now();
|
|
313
|
+
this.emit("trackStart", player, track);
|
|
314
|
+
});
|
|
211
315
|
player.on("trackEnd", (track) => this.emit("trackEnd", player, track));
|
|
212
316
|
player.on("queueEnd", () => this.emit("queueEnd", player));
|
|
213
317
|
player.on("playerError", (error, track) => this.emit("playerError", player, error, track));
|
|
214
318
|
player.on("connectionError", (error) => this.emit("connectionError", player, error));
|
|
215
|
-
player.on("volumeChange", (
|
|
319
|
+
player.on("volumeChange", (oldVol, newVol) => this.emit("volumeChange", player, oldVol, newVol));
|
|
216
320
|
player.on("queueAdd", (track) => this.emit("queueAdd", player, track));
|
|
217
321
|
player.on("queueAddList", (tracks) => this.emit("queueAddList", player, tracks));
|
|
218
322
|
player.on("queueRemove", (track, index) => this.emit("queueRemove", player, track, index));
|
|
@@ -222,6 +326,7 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
222
326
|
player.on("playerDestroy", () => {
|
|
223
327
|
this.emit("playerDestroy", player);
|
|
224
328
|
this.players.delete(guildId);
|
|
329
|
+
this.debug(`Player destroyed for guildId: ${guildId}`);
|
|
225
330
|
});
|
|
226
331
|
player.on("ttsStart", (payload) => this.emit("ttsStart", player, payload));
|
|
227
332
|
player.on("ttsEnd", () => this.emit("ttsEnd", player));
|
|
@@ -230,133 +335,193 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
230
335
|
this.emit("debug", ...args);
|
|
231
336
|
}
|
|
232
337
|
});
|
|
233
|
-
this.players.set(guildId, player);
|
|
234
|
-
return player;
|
|
235
338
|
}
|
|
236
339
|
/**
|
|
237
340
|
* Get an existing player for a guild
|
|
238
341
|
*
|
|
239
342
|
* @param {string | {id: string}} guildOrId - Guild ID or guild object
|
|
240
343
|
* @returns {Player | undefined} The player instance or undefined if not found
|
|
241
|
-
* @example
|
|
242
|
-
* // Get player by guild ID
|
|
243
|
-
* const player = manager.get(guildId);
|
|
244
|
-
* if (player) {
|
|
245
|
-
* await player.play("Never Gonna Give You Up", userId);
|
|
246
|
-
* } else {
|
|
247
|
-
* console.log("No player found for this guild");
|
|
248
|
-
* }
|
|
249
|
-
*
|
|
250
|
-
* // Get player by guild object
|
|
251
|
-
* const playerFromGuild = manager.get(guild);
|
|
252
|
-
* if (playerFromGuild) {
|
|
253
|
-
* playerFromGuild.setVolume(0.5);
|
|
254
|
-
* }
|
|
255
|
-
*
|
|
256
|
-
* // Check if player exists before using
|
|
257
|
-
* const existingPlayer = manager.get(guildId);
|
|
258
|
-
* if (existingPlayer && existingPlayer.playing) {
|
|
259
|
-
* existingPlayer.pause();
|
|
260
|
-
* }
|
|
261
344
|
*/
|
|
262
345
|
get(guildOrId) {
|
|
263
346
|
const guildId = this.resolveGuildId(guildOrId);
|
|
264
|
-
|
|
347
|
+
const player = this.players.get(guildId);
|
|
348
|
+
if (player) {
|
|
349
|
+
player._lastActivity = Date.now();
|
|
350
|
+
}
|
|
351
|
+
return player;
|
|
265
352
|
}
|
|
266
353
|
/**
|
|
267
|
-
* Get an existing player for a guild
|
|
268
|
-
*
|
|
269
|
-
* @param {string | {id: string}} guildOrId - Guild ID or guild object
|
|
270
|
-
* @returns {Player | undefined} The player instance or undefined
|
|
271
|
-
* @example
|
|
272
|
-
* const player = manager.get(guildId);
|
|
273
|
-
* if (player) {
|
|
274
|
-
* await player.play("song name", userId);
|
|
275
|
-
* }
|
|
354
|
+
* Get an existing player for a guild (alias for get)
|
|
276
355
|
*/
|
|
277
356
|
getPlayer(guildOrId) {
|
|
278
|
-
|
|
279
|
-
return this.players.get(guildId);
|
|
357
|
+
return this.get(guildOrId);
|
|
280
358
|
}
|
|
281
359
|
/**
|
|
282
360
|
* Get all players
|
|
283
361
|
*
|
|
284
362
|
* @returns {Player[]} All player instances
|
|
285
|
-
* @example
|
|
286
|
-
* const players = manager.getall();
|
|
287
|
-
* console.log(`Players: ${players.length}`);
|
|
288
363
|
*/
|
|
289
|
-
|
|
364
|
+
getAll() {
|
|
290
365
|
return Array.from(this.players.values());
|
|
291
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Alias for getAll
|
|
369
|
+
*/
|
|
370
|
+
getall() {
|
|
371
|
+
return this.getAll();
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get players by filter
|
|
375
|
+
*
|
|
376
|
+
* @param {(player: Player) => boolean} filter - Filter function
|
|
377
|
+
* @returns {Player[]} Filtered player instances
|
|
378
|
+
*/
|
|
379
|
+
getPlayersByFilter(filter) {
|
|
380
|
+
return this.getAll().filter(filter);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Get players in a voice channel
|
|
384
|
+
*
|
|
385
|
+
* @param {string} channelId - Voice channel ID
|
|
386
|
+
* @returns {Player[]} Players in the channel
|
|
387
|
+
*/
|
|
388
|
+
getPlayersInChannel(channelId) {
|
|
389
|
+
return this.getAll().filter((p) => p.connection?.joinConfig.channelId === channelId);
|
|
390
|
+
}
|
|
292
391
|
/**
|
|
293
392
|
* Destroy a player and clean up resources
|
|
294
393
|
*
|
|
295
394
|
* @param {string | {id: string}} guildOrId - Guild ID or guild object
|
|
296
395
|
* @returns {boolean} True if player was destroyed, false if not found
|
|
297
|
-
* @example
|
|
298
|
-
* // Destroy player by guild ID
|
|
299
|
-
* const destroyed = manager.delete(guildId);
|
|
300
|
-
* if (destroyed) {
|
|
301
|
-
* console.log("Player destroyed successfully");
|
|
302
|
-
* } else {
|
|
303
|
-
* console.log("No player found to destroy");
|
|
304
|
-
* }
|
|
305
|
-
*
|
|
306
|
-
* // Destroy player by guild object
|
|
307
|
-
* const destroyedFromGuild = manager.delete(guild);
|
|
308
|
-
* console.log(`Player destroyed: ${destroyedFromGuild}`);
|
|
309
|
-
*
|
|
310
|
-
* // Clean up all players
|
|
311
|
-
* for (const [guildId, player] of manager.players) {
|
|
312
|
-
* const destroyed = manager.delete(guildId);
|
|
313
|
-
* console.log(`Destroyed player for ${guildId}: ${destroyed}`);
|
|
314
|
-
* }
|
|
315
396
|
*/
|
|
316
397
|
delete(guildOrId) {
|
|
317
398
|
const guildId = this.resolveGuildId(guildOrId);
|
|
318
399
|
const player = this.players.get(guildId);
|
|
319
400
|
if (player) {
|
|
320
|
-
this.debug(`
|
|
401
|
+
this.debug(`Deleting player for guildId: ${guildId}`);
|
|
321
402
|
player.destroy();
|
|
322
|
-
return
|
|
403
|
+
return true;
|
|
323
404
|
}
|
|
324
405
|
return false;
|
|
325
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Destroy multiple players by filter
|
|
409
|
+
*
|
|
410
|
+
* @param {(player: Player) => boolean} filter - Filter function
|
|
411
|
+
* @returns {number} Number of players destroyed
|
|
412
|
+
*/
|
|
413
|
+
deleteWhere(filter) {
|
|
414
|
+
const toDelete = this.getPlayersByFilter(filter);
|
|
415
|
+
let count = 0;
|
|
416
|
+
for (const player of toDelete) {
|
|
417
|
+
const guildId = player.guildId;
|
|
418
|
+
player.destroy();
|
|
419
|
+
this.players.delete(guildId);
|
|
420
|
+
count++;
|
|
421
|
+
}
|
|
422
|
+
if (count > 0) {
|
|
423
|
+
this.debug(`Deleted ${count} players by filter`);
|
|
424
|
+
}
|
|
425
|
+
return count;
|
|
426
|
+
}
|
|
326
427
|
/**
|
|
327
428
|
* Check if a player exists for a guild
|
|
328
429
|
*
|
|
329
430
|
* @param {string | {id: string}} guildOrId - Guild ID or guild object
|
|
330
|
-
* @returns {boolean} True if player exists
|
|
331
|
-
* @example
|
|
332
|
-
* const exists = manager.has(guildId);
|
|
333
|
-
* console.log(`Player exists: ${exists}`);
|
|
431
|
+
* @returns {boolean} True if player exists
|
|
334
432
|
*/
|
|
335
433
|
has(guildOrId) {
|
|
336
434
|
const guildId = this.resolveGuildId(guildOrId);
|
|
337
435
|
return this.players.has(guildId);
|
|
338
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Get number of players
|
|
439
|
+
*/
|
|
339
440
|
get size() {
|
|
340
441
|
return this.players.size;
|
|
341
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Check if debug is enabled
|
|
445
|
+
*/
|
|
342
446
|
get debugEnabled() {
|
|
343
447
|
return this.B_debug;
|
|
344
448
|
}
|
|
345
449
|
/**
|
|
346
|
-
*
|
|
450
|
+
* Get manager statistics
|
|
451
|
+
*
|
|
452
|
+
* @returns {PlayerStats} Statistics about players
|
|
453
|
+
*/
|
|
454
|
+
getStats() {
|
|
455
|
+
let activePlayers = 0;
|
|
456
|
+
let pausedPlayers = 0;
|
|
457
|
+
let connectedPlayers = 0;
|
|
458
|
+
let totalTracksInQueue = 0;
|
|
459
|
+
for (const player of this.players.values()) {
|
|
460
|
+
if (player.isPlaying)
|
|
461
|
+
activePlayers++;
|
|
462
|
+
if (player.isPaused)
|
|
463
|
+
pausedPlayers++;
|
|
464
|
+
if (player.connection)
|
|
465
|
+
connectedPlayers++;
|
|
466
|
+
totalTracksInQueue += player.queueSize;
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
totalPlayers: this.players.size,
|
|
470
|
+
activePlayers,
|
|
471
|
+
pausedPlayers,
|
|
472
|
+
connectedPlayers,
|
|
473
|
+
totalTracksInQueue,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Broadcast an action to all players
|
|
347
478
|
*
|
|
348
|
-
* @
|
|
479
|
+
* @param {string} action - Action to perform
|
|
480
|
+
* @param {...any[]} args - Arguments for the action
|
|
349
481
|
* @example
|
|
350
|
-
* manager.
|
|
351
|
-
*
|
|
482
|
+
* manager.broadcast("setVolume", 50);
|
|
483
|
+
* manager.broadcast("pause");
|
|
484
|
+
*/
|
|
485
|
+
broadcast(action, ...args) {
|
|
486
|
+
for (const player of this.players.values()) {
|
|
487
|
+
if (typeof player[action] === "function") {
|
|
488
|
+
try {
|
|
489
|
+
player[action](...args);
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
this.debug(`Error broadcasting ${action} to ${player.guildId}:`, error);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Destroy all players and clean up
|
|
352
499
|
*/
|
|
353
500
|
destroy() {
|
|
354
|
-
this.debug(`
|
|
501
|
+
this.debug(`Destroying all players`);
|
|
502
|
+
if (this.persistenceManager) {
|
|
503
|
+
this.persistenceManager.saveAll().catch((err) => {
|
|
504
|
+
this.debug("Failed to save players before destroy:", err);
|
|
505
|
+
});
|
|
506
|
+
this.persistenceManager.shutdown().catch(console.error);
|
|
507
|
+
}
|
|
508
|
+
// Stop cleanup intervals
|
|
509
|
+
if (this.cleanupInterval) {
|
|
510
|
+
clearInterval(this.cleanupInterval);
|
|
511
|
+
this.cleanupInterval = null;
|
|
512
|
+
}
|
|
513
|
+
if (this.statsInterval) {
|
|
514
|
+
clearInterval(this.statsInterval);
|
|
515
|
+
this.statsInterval = null;
|
|
516
|
+
}
|
|
517
|
+
// Destroy all players
|
|
355
518
|
for (const player of this.players.values()) {
|
|
356
519
|
player.destroy();
|
|
357
520
|
}
|
|
358
521
|
this.players.clear();
|
|
522
|
+
this.searchCache.clear();
|
|
359
523
|
this.removeAllListeners();
|
|
524
|
+
this.debug(`PlayerManager destroyed`);
|
|
360
525
|
}
|
|
361
526
|
/**
|
|
362
527
|
* Search using registered plugins without creating a Player.
|
|
@@ -364,28 +529,158 @@ class PlayerManager extends events_1.EventEmitter {
|
|
|
364
529
|
* @param {string} query - The query to search for
|
|
365
530
|
* @param {string} requestedBy - The user ID who requested the search
|
|
366
531
|
* @returns {Promise<SearchResult>} The search result
|
|
367
|
-
* @example
|
|
368
|
-
* const result = await manager.search("Never Gonna Give You Up", userId);
|
|
369
|
-
* console.log(`Search result: ${result.tracks.length} tracks`);
|
|
370
532
|
*/
|
|
371
533
|
async search(query, requestedBy) {
|
|
372
|
-
this.debug(`
|
|
534
|
+
this.debug(`Search called with query: ${query}, requestedBy: ${requestedBy}`);
|
|
535
|
+
// Check cache first
|
|
536
|
+
const cached = this.getCachedSearch(query);
|
|
537
|
+
if (cached) {
|
|
538
|
+
return cached;
|
|
539
|
+
}
|
|
373
540
|
const plugin = this.plugins.find((p) => p.canHandle(query));
|
|
374
541
|
if (!plugin) {
|
|
375
|
-
this.debug(`
|
|
542
|
+
this.debug(`No plugin found to handle: ${query}`);
|
|
376
543
|
throw new Error(`No plugin found to handle: ${query}`);
|
|
377
544
|
}
|
|
378
545
|
try {
|
|
379
|
-
|
|
546
|
+
const result = await this.withTimeout(plugin.search(query, requestedBy), "Search operation timed out");
|
|
547
|
+
this.setCachedSearch(query, result);
|
|
548
|
+
return result;
|
|
380
549
|
}
|
|
381
550
|
catch (error) {
|
|
382
|
-
this.debug(`
|
|
551
|
+
this.debug(`Search error:`, error);
|
|
383
552
|
throw error;
|
|
384
553
|
}
|
|
385
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Clear search cache
|
|
557
|
+
*/
|
|
558
|
+
clearSearchCache() {
|
|
559
|
+
const size = this.searchCache.size;
|
|
560
|
+
this.searchCache.clear();
|
|
561
|
+
this.debug(`Cleared ${size} search cache entries`);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Register a plugin after initialization
|
|
565
|
+
*
|
|
566
|
+
* @param {SourcePlugin} plugin - Plugin to register
|
|
567
|
+
*/
|
|
568
|
+
registerPlugin(plugin) {
|
|
569
|
+
this.plugins.push(plugin);
|
|
570
|
+
this.debug(`Registered plugin: ${plugin.name}`);
|
|
571
|
+
// Register plugin with all existing players
|
|
572
|
+
for (const player of this.players.values()) {
|
|
573
|
+
player.addPlugin(plugin);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Unregister a plugin
|
|
578
|
+
*
|
|
579
|
+
* @param {string} name - Plugin name to unregister
|
|
580
|
+
* @returns {boolean} True if plugin was unregistered
|
|
581
|
+
*/
|
|
582
|
+
unregisterPlugin(name) {
|
|
583
|
+
const index = this.plugins.findIndex((p) => p.name === name);
|
|
584
|
+
if (index === -1)
|
|
585
|
+
return false;
|
|
586
|
+
this.plugins.splice(index, 1);
|
|
587
|
+
this.debug(`Unregistered plugin: ${name}`);
|
|
588
|
+
// Note: Cannot easily remove plugins from existing players
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get all registered plugins
|
|
593
|
+
*/
|
|
594
|
+
getPlugins() {
|
|
595
|
+
return [...this.plugins];
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Register an extension after initialization
|
|
599
|
+
*
|
|
600
|
+
* @param {BaseExtension} extension - Extension to register
|
|
601
|
+
*/
|
|
602
|
+
registerExtension(extension) {
|
|
603
|
+
this.extensions.push(extension);
|
|
604
|
+
this.debug(`Registered extension: ${extension.name}`);
|
|
605
|
+
// Register extension with all existing players
|
|
606
|
+
for (const player of this.players.values()) {
|
|
607
|
+
player.attachExtension(extension);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Get manager configuration
|
|
612
|
+
*/
|
|
613
|
+
getConfig() {
|
|
614
|
+
return {
|
|
615
|
+
extractorTimeout: this.extractorTimeout,
|
|
616
|
+
autoCleanup: this.autoCleanup,
|
|
617
|
+
cleanupTimeout: this.cleanupTimeout,
|
|
618
|
+
enableSearchCache: this.enableSearchCache,
|
|
619
|
+
pluginsCount: this.plugins.length,
|
|
620
|
+
extensionsCount: this.extensions.length,
|
|
621
|
+
playersCount: this.players.size,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
initPersistence(persistenceOptions) {
|
|
625
|
+
this.persistenceManager = new PersistenceManager_1.PersistenceManager(this, persistenceOptions);
|
|
626
|
+
// Forward persistence events
|
|
627
|
+
this.persistenceManager.on("playerSaved", (guildId) => {
|
|
628
|
+
this.emit("playerSaved", guildId);
|
|
629
|
+
});
|
|
630
|
+
this.persistenceManager.on("playerLoaded", (guildId, data) => {
|
|
631
|
+
this.emit("playerLoaded", guildId, data);
|
|
632
|
+
});
|
|
633
|
+
this.persistenceManager.on("savedAll", (results) => {
|
|
634
|
+
this.emit("savedAll", results);
|
|
635
|
+
});
|
|
636
|
+
this.persistenceManager.on("loadedAll", (results) => {
|
|
637
|
+
this.emit("loadedAll", results);
|
|
638
|
+
});
|
|
639
|
+
this.debug("Persistence manager initialized");
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Get persistence manager
|
|
643
|
+
*/
|
|
644
|
+
getPersistence() {
|
|
645
|
+
return this.persistenceManager;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Save all players
|
|
649
|
+
*/
|
|
650
|
+
async saveAllPlayers() {
|
|
651
|
+
if (!this.persistenceManager) {
|
|
652
|
+
throw new Error("Persistence not enabled");
|
|
653
|
+
}
|
|
654
|
+
return await this.persistenceManager.saveAll();
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Load all players
|
|
658
|
+
*/
|
|
659
|
+
async loadAllPlayers(restorePosition = true) {
|
|
660
|
+
if (!this.persistenceManager) {
|
|
661
|
+
throw new Error("Persistence not enabled");
|
|
662
|
+
}
|
|
663
|
+
return await this.persistenceManager.loadAll(restorePosition);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Save a specific player
|
|
667
|
+
*/
|
|
668
|
+
async savePlayer(guildId) {
|
|
669
|
+
if (!this.persistenceManager)
|
|
670
|
+
return false;
|
|
671
|
+
const player = this.get(guildId);
|
|
672
|
+
if (!player)
|
|
673
|
+
return false;
|
|
674
|
+
return await this.persistenceManager.savePlayer(player);
|
|
675
|
+
}
|
|
386
676
|
}
|
|
387
677
|
exports.PlayerManager = PlayerManager;
|
|
388
678
|
PlayerManager.instance = null;
|
|
679
|
+
/**
|
|
680
|
+
* Get the global PlayerManager instance
|
|
681
|
+
*
|
|
682
|
+
* @returns {PlayerManager | null} Global instance or null
|
|
683
|
+
*/
|
|
389
684
|
function getInstance() {
|
|
390
685
|
const globalInst = (0, exports.getGlobalManager)();
|
|
391
686
|
if (!globalInst) {
|