@elizaos/plugin-music 2.0.0-beta.1 → 2.0.11-beta.7
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/LICENSE +21 -0
- package/README.md +127 -144
- package/package.json +22 -5
- package/dist/index.d.ts +0 -2263
- package/dist/index.js +0 -15104
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
DELETED
|
@@ -1,2263 +0,0 @@
|
|
|
1
|
-
import { Action, UUID, IAgentRuntime, AgentRuntime, Service, Plugin } from '@elizaos/core';
|
|
2
|
-
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { Readable, PassThrough } from 'node:stream';
|
|
4
|
-
import { ServerResponse } from 'node:http';
|
|
5
|
-
|
|
6
|
-
declare const musicAction: Action;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Analytics data for a guild/room
|
|
10
|
-
*/
|
|
11
|
-
interface DJAnalytics {
|
|
12
|
-
totalTracksPlayed: number;
|
|
13
|
-
totalPlayTime: number;
|
|
14
|
-
mostPlayedTracks: Array<{
|
|
15
|
-
url: string;
|
|
16
|
-
title: string;
|
|
17
|
-
playCount: number;
|
|
18
|
-
lastPlayed: number;
|
|
19
|
-
}>;
|
|
20
|
-
mostRequestedBy: Array<{
|
|
21
|
-
entityId: UUID;
|
|
22
|
-
name: string;
|
|
23
|
-
requestCount: number;
|
|
24
|
-
}>;
|
|
25
|
-
popularTimes: Array<{
|
|
26
|
-
hour: number;
|
|
27
|
-
playCount: number;
|
|
28
|
-
}>;
|
|
29
|
-
popularDays: Array<{
|
|
30
|
-
day: number;
|
|
31
|
-
playCount: number;
|
|
32
|
-
}>;
|
|
33
|
-
milestones: Array<{
|
|
34
|
-
type: string;
|
|
35
|
-
value: number;
|
|
36
|
-
timestamp: number;
|
|
37
|
-
}>;
|
|
38
|
-
sessionStats: {
|
|
39
|
-
totalSessions: number;
|
|
40
|
-
averageSessionDuration: number;
|
|
41
|
-
longestSession: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
interface ListenerSnapshot {
|
|
45
|
-
timestamp: number;
|
|
46
|
-
listenerCount: number;
|
|
47
|
-
humanListenerCount: number;
|
|
48
|
-
botListenerCount: number;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Get analytics for a guild/room
|
|
52
|
-
*/
|
|
53
|
-
declare function getAnalytics(runtime: IAgentRuntime, roomId: UUID): Promise<DJAnalytics | null>;
|
|
54
|
-
/**
|
|
55
|
-
* Track a track being played
|
|
56
|
-
*/
|
|
57
|
-
declare function trackTrackPlayed(runtime: IAgentRuntime, roomId: UUID, track: {
|
|
58
|
-
url: string;
|
|
59
|
-
title: string;
|
|
60
|
-
}, duration: number, requestedBy?: {
|
|
61
|
-
entityId: UUID;
|
|
62
|
-
name: string;
|
|
63
|
-
}): Promise<void>;
|
|
64
|
-
/**
|
|
65
|
-
* Track a listening session
|
|
66
|
-
*/
|
|
67
|
-
declare function trackSession(runtime: IAgentRuntime, roomId: UUID, duration: number): Promise<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Track a listener snapshot for analytics
|
|
70
|
-
* Called by the listener tracking service in plugin-radio
|
|
71
|
-
*/
|
|
72
|
-
declare function trackListenerSnapshot(runtime: IAgentRuntime, roomId: UUID, snapshot: ListenerSnapshot): Promise<void>;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Per-Guild DJ Settings
|
|
76
|
-
* Comprehensive settings for DJ behavior specific to each guild/room
|
|
77
|
-
*/
|
|
78
|
-
interface DJGuildSettings {
|
|
79
|
-
autonomyLevel?: "MANUAL" | "BALANCED" | "AUTONOMOUS" | "RADIO";
|
|
80
|
-
enabled: boolean;
|
|
81
|
-
autoFillEnabled: boolean;
|
|
82
|
-
autoFillThreshold?: number;
|
|
83
|
-
timeBasedProgramming: boolean;
|
|
84
|
-
repetitionControlEnabled: boolean;
|
|
85
|
-
introsEnabled: boolean;
|
|
86
|
-
commentaryEnabled: boolean;
|
|
87
|
-
useLLMForCommentary: boolean;
|
|
88
|
-
jokesSetting: boolean;
|
|
89
|
-
autoTriviaEnabled: boolean;
|
|
90
|
-
autoTriviaMinTracks?: number;
|
|
91
|
-
autoTriviaMinMinutes?: number;
|
|
92
|
-
autoJokesEnabled: boolean;
|
|
93
|
-
autoJokesMinTracks?: number;
|
|
94
|
-
autoJokesMinMinutes?: number;
|
|
95
|
-
autoRecapEnabled: boolean;
|
|
96
|
-
autoRecapMinTracks?: number;
|
|
97
|
-
autoRecapMinMinutes?: number;
|
|
98
|
-
stationName?: string;
|
|
99
|
-
stationDescription?: string;
|
|
100
|
-
listenerTrackingEnabled: boolean;
|
|
101
|
-
audioQuality?: "high" | "medium" | "low";
|
|
102
|
-
crossFadeEnabled: boolean;
|
|
103
|
-
crossFadeDuration?: number;
|
|
104
|
-
createdAt: number;
|
|
105
|
-
updatedAt: number;
|
|
106
|
-
createdBy?: UUID;
|
|
107
|
-
lastModifiedBy?: UUID;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Default guild settings
|
|
111
|
-
*/
|
|
112
|
-
declare const DEFAULT_GUILD_SETTINGS: DJGuildSettings;
|
|
113
|
-
/**
|
|
114
|
-
* Get DJ guild settings for a room
|
|
115
|
-
*/
|
|
116
|
-
declare function getDJGuildSettings(runtime: IAgentRuntime, roomId: UUID): Promise<DJGuildSettings>;
|
|
117
|
-
/**
|
|
118
|
-
* Set DJ guild settings for a room
|
|
119
|
-
*/
|
|
120
|
-
declare function setDJGuildSettings(runtime: IAgentRuntime, roomId: UUID, settings: Partial<DJGuildSettings>, modifiedBy?: UUID): Promise<void>;
|
|
121
|
-
/**
|
|
122
|
-
* Reset DJ guild settings to defaults
|
|
123
|
-
*/
|
|
124
|
-
declare function resetDJGuildSettings(runtime: IAgentRuntime, roomId: UUID, modifiedBy?: UUID): Promise<void>;
|
|
125
|
-
/**
|
|
126
|
-
* Enable/disable DJ for a guild
|
|
127
|
-
*/
|
|
128
|
-
declare function toggleDJ(runtime: IAgentRuntime, roomId: UUID, enabled: boolean, modifiedBy?: UUID): Promise<void>;
|
|
129
|
-
/**
|
|
130
|
-
* Set autonomy level for a guild
|
|
131
|
-
*/
|
|
132
|
-
declare function setAutonomyLevel(runtime: IAgentRuntime, roomId: UUID, level: "MANUAL" | "BALANCED" | "AUTONOMOUS" | "RADIO", modifiedBy?: UUID): Promise<void>;
|
|
133
|
-
/**
|
|
134
|
-
* Get all configured guilds
|
|
135
|
-
*/
|
|
136
|
-
declare function getAllConfiguredGuilds(_runtime: IAgentRuntime): Promise<Array<{
|
|
137
|
-
roomId: UUID;
|
|
138
|
-
settings: DJGuildSettings;
|
|
139
|
-
}>>;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* DJ Intro Options
|
|
143
|
-
* Advanced configuration for DJ track introductions and commentary
|
|
144
|
-
*/
|
|
145
|
-
interface DJIntroOptions {
|
|
146
|
-
useLLM: boolean;
|
|
147
|
-
llmModel?: string;
|
|
148
|
-
style: "concise" | "detailed" | "casual" | "professional" | "energetic" | "chill";
|
|
149
|
-
personality?: string;
|
|
150
|
-
includeJokes: boolean;
|
|
151
|
-
jokeFrequency?: number;
|
|
152
|
-
includeFunFacts: boolean;
|
|
153
|
-
includeArtistInfo: boolean;
|
|
154
|
-
includeDedications: boolean;
|
|
155
|
-
dedicationStyle?: "brief" | "heartfelt" | "casual";
|
|
156
|
-
introDuration: "short" | "medium" | "long";
|
|
157
|
-
skipIntroChance?: number;
|
|
158
|
-
minTracksBetweenIntros?: number;
|
|
159
|
-
customTemplates?: string[];
|
|
160
|
-
templateVariety: boolean;
|
|
161
|
-
contextWindow?: number;
|
|
162
|
-
musicInfoIntegration: boolean;
|
|
163
|
-
listenerCountIntegration: boolean;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Default DJ intro options
|
|
167
|
-
*/
|
|
168
|
-
declare const DEFAULT_DJ_INTRO_OPTIONS: DJIntroOptions;
|
|
169
|
-
/**
|
|
170
|
-
* Get DJ intro options for a room
|
|
171
|
-
*/
|
|
172
|
-
declare function getDJIntroOptions(runtime: IAgentRuntime, roomId: UUID): Promise<DJIntroOptions>;
|
|
173
|
-
/**
|
|
174
|
-
* Set DJ intro options for a room
|
|
175
|
-
*/
|
|
176
|
-
declare function setDJIntroOptions(runtime: IAgentRuntime, roomId: UUID, options: Partial<DJIntroOptions>): Promise<void>;
|
|
177
|
-
/**
|
|
178
|
-
* Reset DJ intro options to defaults for a room
|
|
179
|
-
*/
|
|
180
|
-
declare function resetDJIntroOptions(runtime: IAgentRuntime, roomId: UUID): Promise<void>;
|
|
181
|
-
/**
|
|
182
|
-
* Get intro prompt based on options
|
|
183
|
-
*/
|
|
184
|
-
declare function buildIntroPrompt(options: DJIntroOptions, context: {
|
|
185
|
-
characterName: string;
|
|
186
|
-
trackTitle: string;
|
|
187
|
-
artistName?: string;
|
|
188
|
-
albumName?: string;
|
|
189
|
-
dedicatedTo?: string;
|
|
190
|
-
dedicationMessage?: string;
|
|
191
|
-
listenerCount?: number;
|
|
192
|
-
previousTracks?: string[];
|
|
193
|
-
}): string;
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* DJ Tip record
|
|
197
|
-
*/
|
|
198
|
-
interface DJTip {
|
|
199
|
-
from: string;
|
|
200
|
-
fromUserId: string;
|
|
201
|
-
amount: number;
|
|
202
|
-
currency: string;
|
|
203
|
-
message?: string;
|
|
204
|
-
timestamp: number;
|
|
205
|
-
transactionId?: string;
|
|
206
|
-
roomId?: UUID;
|
|
207
|
-
}
|
|
208
|
-
interface TopTipper {
|
|
209
|
-
userId: string;
|
|
210
|
-
username: string;
|
|
211
|
-
totalAmount: number;
|
|
212
|
-
currency: string;
|
|
213
|
-
tipCount: number;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* DJ Tip Statistics
|
|
217
|
-
*/
|
|
218
|
-
interface DJTipStats {
|
|
219
|
-
totalTips: number;
|
|
220
|
-
totalAmount: Record<string, number>;
|
|
221
|
-
tips: DJTip[];
|
|
222
|
-
topTippers: TopTipper[];
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Track a DJ tip
|
|
226
|
-
*/
|
|
227
|
-
declare function trackDJTip(runtime: IAgentRuntime, roomId: UUID, tip: Omit<DJTip, "roomId">): Promise<void>;
|
|
228
|
-
/**
|
|
229
|
-
* Get DJ tip statistics
|
|
230
|
-
*/
|
|
231
|
-
declare function getDJTipStats(runtime: IAgentRuntime): Promise<DJTipStats>;
|
|
232
|
-
/**
|
|
233
|
-
* Get recent tips
|
|
234
|
-
*/
|
|
235
|
-
declare function getRecentTips(runtime: IAgentRuntime, limit?: number): Promise<DJTip[]>;
|
|
236
|
-
/**
|
|
237
|
-
* Get top tippers
|
|
238
|
-
*/
|
|
239
|
-
declare function getTopTippers(runtime: IAgentRuntime, limit?: number): Promise<DJTipStats["topTippers"]>;
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Represents a song in the global music library
|
|
243
|
-
*/
|
|
244
|
-
interface LibrarySong {
|
|
245
|
-
id: string;
|
|
246
|
-
url: string;
|
|
247
|
-
title: string;
|
|
248
|
-
artist?: string;
|
|
249
|
-
channel?: string;
|
|
250
|
-
duration?: number;
|
|
251
|
-
playCount: number;
|
|
252
|
-
lastPlayed: number;
|
|
253
|
-
firstAdded: number;
|
|
254
|
-
requestedBy: Set<string>;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Get a song from the library by URL
|
|
258
|
-
*/
|
|
259
|
-
declare function getSong(runtime: IAgentRuntime, url: string): Promise<LibrarySong | null>;
|
|
260
|
-
/**
|
|
261
|
-
* Add or update a song in the library
|
|
262
|
-
*/
|
|
263
|
-
declare function addSongToLibrary(runtime: IAgentRuntime, songData: {
|
|
264
|
-
url: string;
|
|
265
|
-
title: string;
|
|
266
|
-
artist?: string;
|
|
267
|
-
channel?: string;
|
|
268
|
-
duration?: number;
|
|
269
|
-
requestedBy?: string;
|
|
270
|
-
}): Promise<LibrarySong>;
|
|
271
|
-
/**
|
|
272
|
-
* Get recent songs from the library (sorted by last played)
|
|
273
|
-
*/
|
|
274
|
-
declare function getRecentSongs(runtime: IAgentRuntime, limit?: number): Promise<LibrarySong[]>;
|
|
275
|
-
/**
|
|
276
|
-
* Search the library for songs matching a query
|
|
277
|
-
*/
|
|
278
|
-
declare function searchLibrary(runtime: IAgentRuntime, query: string, limit?: number): Promise<LibrarySong[]>;
|
|
279
|
-
/**
|
|
280
|
-
* Get the most recently played song
|
|
281
|
-
*/
|
|
282
|
-
declare function getLastPlayedSong(runtime: IAgentRuntime): Promise<LibrarySong | null>;
|
|
283
|
-
/**
|
|
284
|
-
* Get all songs sorted by play count
|
|
285
|
-
*/
|
|
286
|
-
declare function getMostPlayedSongs(runtime: IAgentRuntime, limit?: number): Promise<LibrarySong[]>;
|
|
287
|
-
/**
|
|
288
|
-
* Get library statistics
|
|
289
|
-
*/
|
|
290
|
-
declare function getLibraryStats(runtime: IAgentRuntime): Promise<{
|
|
291
|
-
totalSongs: number;
|
|
292
|
-
totalPlays: number;
|
|
293
|
-
mostPlayed?: LibrarySong;
|
|
294
|
-
}>;
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Represents a track in a playlist
|
|
298
|
-
*/
|
|
299
|
-
interface PlaylistTrack {
|
|
300
|
-
url: string;
|
|
301
|
-
title: string;
|
|
302
|
-
duration?: number;
|
|
303
|
-
addedAt?: number;
|
|
304
|
-
requestedBy?: string;
|
|
305
|
-
dedicatedTo?: string;
|
|
306
|
-
dedicationMessage?: string;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Represents a saved playlist
|
|
310
|
-
*/
|
|
311
|
-
interface Playlist {
|
|
312
|
-
id: string;
|
|
313
|
-
name: string;
|
|
314
|
-
tracks: PlaylistTrack[];
|
|
315
|
-
createdAt: number;
|
|
316
|
-
updatedAt: number;
|
|
317
|
-
isFavorite?: boolean;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Save a playlist to user's entity components
|
|
321
|
-
*/
|
|
322
|
-
declare function savePlaylist(runtime: IAgentRuntime, entityId: UUID, playlist: Omit<Playlist, "id" | "createdAt" | "updatedAt"> & {
|
|
323
|
-
id?: string;
|
|
324
|
-
createdAt?: number;
|
|
325
|
-
}): Promise<Playlist>;
|
|
326
|
-
/**
|
|
327
|
-
* Load all playlists for a user
|
|
328
|
-
*/
|
|
329
|
-
declare function loadPlaylists(runtime: IAgentRuntime, entityId: UUID): Promise<Playlist[]>;
|
|
330
|
-
/**
|
|
331
|
-
* Delete a playlist
|
|
332
|
-
*/
|
|
333
|
-
declare function deletePlaylist(runtime: IAgentRuntime, entityId: UUID, playlistId: string): Promise<boolean>;
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* User music preferences
|
|
337
|
-
*/
|
|
338
|
-
interface UserMusicPreferences {
|
|
339
|
-
favoriteGenres?: string[];
|
|
340
|
-
favoriteArtists?: string[];
|
|
341
|
-
favoriteTracks?: Array<{
|
|
342
|
-
url: string;
|
|
343
|
-
title: string;
|
|
344
|
-
playCount?: number;
|
|
345
|
-
}>;
|
|
346
|
-
dislikedTracks?: string[];
|
|
347
|
-
skipHistory?: Array<{
|
|
348
|
-
url: string;
|
|
349
|
-
timestamp: number;
|
|
350
|
-
}>;
|
|
351
|
-
requestHistory?: Array<{
|
|
352
|
-
url: string;
|
|
353
|
-
title: string;
|
|
354
|
-
timestamp: number;
|
|
355
|
-
}>;
|
|
356
|
-
listeningSessions?: Array<{
|
|
357
|
-
startTime: number;
|
|
358
|
-
endTime?: number;
|
|
359
|
-
tracksPlayed: number;
|
|
360
|
-
}>;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Update user music preferences
|
|
364
|
-
*/
|
|
365
|
-
declare function updateUserPreferences(runtime: IAgentRuntime, entityId: UUID, preferences: Partial<UserMusicPreferences>, roomId?: UUID, worldId?: UUID): Promise<UserMusicPreferences>;
|
|
366
|
-
/**
|
|
367
|
-
* Get user music preferences
|
|
368
|
-
*/
|
|
369
|
-
declare function getUserPreferences(runtime: IAgentRuntime, entityId: UUID): Promise<UserMusicPreferences | null>;
|
|
370
|
-
/**
|
|
371
|
-
* Get preferences for all users in a room
|
|
372
|
-
*/
|
|
373
|
-
declare function getRoomPreferences(runtime: IAgentRuntime, roomId: UUID): Promise<Map<UUID, UserMusicPreferences>>;
|
|
374
|
-
/**
|
|
375
|
-
* Track a track request
|
|
376
|
-
*/
|
|
377
|
-
declare function trackTrackRequest(runtime: IAgentRuntime, entityId: UUID, track: {
|
|
378
|
-
url: string;
|
|
379
|
-
title: string;
|
|
380
|
-
}, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
381
|
-
/**
|
|
382
|
-
* Track a skip
|
|
383
|
-
*/
|
|
384
|
-
declare function trackSkip(runtime: IAgentRuntime, entityId: UUID, trackUrl: string, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
385
|
-
/**
|
|
386
|
-
* Track favorite track
|
|
387
|
-
*/
|
|
388
|
-
declare function trackFavorite(runtime: IAgentRuntime, entityId: UUID, track: {
|
|
389
|
-
url: string;
|
|
390
|
-
title: string;
|
|
391
|
-
}, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Track play history for repetition control
|
|
395
|
-
*/
|
|
396
|
-
interface PlayHistoryEntry {
|
|
397
|
-
url: string;
|
|
398
|
-
title: string;
|
|
399
|
-
playedAt: number;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Smart repetition control to avoid playing same songs too frequently
|
|
403
|
-
*/
|
|
404
|
-
declare class RepetitionControl {
|
|
405
|
-
private playHistory;
|
|
406
|
-
private readonly MAX_HISTORY_SIZE;
|
|
407
|
-
private readonly MIN_REPLAY_INTERVAL;
|
|
408
|
-
/**
|
|
409
|
-
* Record a track play
|
|
410
|
-
*/
|
|
411
|
-
recordPlay(guildId: string, url: string, title: string): void;
|
|
412
|
-
/**
|
|
413
|
-
* Check if a track can be played (not played too recently)
|
|
414
|
-
*/
|
|
415
|
-
canPlay(guildId: string, url: string, minInterval?: number): boolean;
|
|
416
|
-
/**
|
|
417
|
-
* Get recently played tracks for a guild
|
|
418
|
-
*/
|
|
419
|
-
getRecentlyPlayed(guildId: string, count?: number): PlayHistoryEntry[];
|
|
420
|
-
/**
|
|
421
|
-
* Filter tracks to avoid repetition
|
|
422
|
-
*/
|
|
423
|
-
filterRepetition(guildId: string, tracks: Array<{
|
|
424
|
-
url: string;
|
|
425
|
-
title: string;
|
|
426
|
-
}>, minInterval?: number): Array<{
|
|
427
|
-
url: string;
|
|
428
|
-
title: string;
|
|
429
|
-
}>;
|
|
430
|
-
/**
|
|
431
|
-
* Get play count for a track in recent history
|
|
432
|
-
*/
|
|
433
|
-
getRecentPlayCount(guildId: string, url: string, timeWindow?: number): number;
|
|
434
|
-
/**
|
|
435
|
-
* Score tracks based on variety (lower score = played less recently)
|
|
436
|
-
*/
|
|
437
|
-
scoreByVariety(guildId: string, tracks: Array<{
|
|
438
|
-
url: string;
|
|
439
|
-
title: string;
|
|
440
|
-
playCount?: number;
|
|
441
|
-
}>): Array<{
|
|
442
|
-
url: string;
|
|
443
|
-
title: string;
|
|
444
|
-
playCount?: number;
|
|
445
|
-
varietyScore: number;
|
|
446
|
-
}>;
|
|
447
|
-
/**
|
|
448
|
-
* Clear history for a guild
|
|
449
|
-
*/
|
|
450
|
-
clearHistory(guildId: string): void;
|
|
451
|
-
/**
|
|
452
|
-
* Get statistics for a guild
|
|
453
|
-
*/
|
|
454
|
-
getStats(guildId: string): {
|
|
455
|
-
totalPlays: number;
|
|
456
|
-
uniqueTracks: number;
|
|
457
|
-
averageRepeatInterval: number;
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
declare const repetitionControl: RepetitionControl;
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Detailed per-song memory and statistics
|
|
464
|
-
* Tracks everything about a song across all rooms
|
|
465
|
-
*/
|
|
466
|
-
interface SongMemory {
|
|
467
|
-
url: string;
|
|
468
|
-
title: string;
|
|
469
|
-
artist?: string;
|
|
470
|
-
album?: string;
|
|
471
|
-
duration?: number;
|
|
472
|
-
totalPlays: number;
|
|
473
|
-
totalPlayTime: number;
|
|
474
|
-
lastPlayed: number;
|
|
475
|
-
firstPlayed: number;
|
|
476
|
-
totalRequests: number;
|
|
477
|
-
uniqueRequesters: number;
|
|
478
|
-
topRequesters: Array<{
|
|
479
|
-
entityId: UUID;
|
|
480
|
-
name: string;
|
|
481
|
-
count: number;
|
|
482
|
-
}>;
|
|
483
|
-
totalLikes: number;
|
|
484
|
-
totalDislikes: number;
|
|
485
|
-
skipCount: number;
|
|
486
|
-
completionRate: number;
|
|
487
|
-
playedInRooms: Array<{
|
|
488
|
-
roomId: UUID;
|
|
489
|
-
playCount: number;
|
|
490
|
-
lastPlayed: number;
|
|
491
|
-
}>;
|
|
492
|
-
dedicationCount: number;
|
|
493
|
-
dedications: Array<{
|
|
494
|
-
from: string;
|
|
495
|
-
to: string;
|
|
496
|
-
message?: string;
|
|
497
|
-
timestamp: number;
|
|
498
|
-
}>;
|
|
499
|
-
popularHours: number[];
|
|
500
|
-
popularDays: number[];
|
|
501
|
-
averageListenerCount: number;
|
|
502
|
-
peakListenerCount: number;
|
|
503
|
-
listenerEngagement: number;
|
|
504
|
-
createdAt: number;
|
|
505
|
-
updatedAt: number;
|
|
506
|
-
tags?: string[];
|
|
507
|
-
notes?: string;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Get song memory
|
|
511
|
-
*/
|
|
512
|
-
declare function getSongMemory(runtime: IAgentRuntime, url: string): Promise<SongMemory | null>;
|
|
513
|
-
/**
|
|
514
|
-
* Record a play
|
|
515
|
-
*/
|
|
516
|
-
declare function recordSongPlay(runtime: IAgentRuntime, song: {
|
|
517
|
-
url: string;
|
|
518
|
-
title: string;
|
|
519
|
-
artist?: string;
|
|
520
|
-
album?: string;
|
|
521
|
-
duration?: number;
|
|
522
|
-
}, context: {
|
|
523
|
-
roomId?: UUID;
|
|
524
|
-
playDuration: number;
|
|
525
|
-
listenerCount?: number;
|
|
526
|
-
requestedBy?: {
|
|
527
|
-
entityId: UUID;
|
|
528
|
-
name: string;
|
|
529
|
-
};
|
|
530
|
-
wasSkipped?: boolean;
|
|
531
|
-
}): Promise<void>;
|
|
532
|
-
/**
|
|
533
|
-
* Record a request
|
|
534
|
-
*/
|
|
535
|
-
declare function recordSongRequest(runtime: IAgentRuntime, song: {
|
|
536
|
-
url: string;
|
|
537
|
-
title: string;
|
|
538
|
-
artist?: string;
|
|
539
|
-
album?: string;
|
|
540
|
-
duration?: number;
|
|
541
|
-
}, requester: {
|
|
542
|
-
entityId: UUID;
|
|
543
|
-
name: string;
|
|
544
|
-
}): Promise<void>;
|
|
545
|
-
/**
|
|
546
|
-
* Record a dedication
|
|
547
|
-
*/
|
|
548
|
-
declare function recordSongDedication(runtime: IAgentRuntime, url: string, dedication: {
|
|
549
|
-
from: string;
|
|
550
|
-
to: string;
|
|
551
|
-
message?: string;
|
|
552
|
-
}): Promise<void>;
|
|
553
|
-
/**
|
|
554
|
-
* Get top songs by play count
|
|
555
|
-
*/
|
|
556
|
-
declare function getTopSongs(_runtime: IAgentRuntime, _limit?: number): Promise<SongMemory[]>;
|
|
557
|
-
/**
|
|
558
|
-
* Get most requested songs
|
|
559
|
-
*/
|
|
560
|
-
declare function getMostRequestedSongs(_runtime: IAgentRuntime, _limit?: number): Promise<SongMemory[]>;
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Audio Broadcast Contracts
|
|
564
|
-
*
|
|
565
|
-
* WHY THIS EXISTS:
|
|
566
|
-
* These contracts define how audio flows from music-player to consumers (Discord, Web, etc.)
|
|
567
|
-
* without creating dependencies between plugins. This allows:
|
|
568
|
-
*
|
|
569
|
-
* 1. Plugin Independence: music-player doesn't know about Discord, Discord doesn't know
|
|
570
|
-
* about music-player internals. They communicate through contracts.
|
|
571
|
-
*
|
|
572
|
-
* 2. Multiple Consumers: Multiple outputs (Discord + 5 web listeners) can all receive
|
|
573
|
-
* the same broadcast independently without affecting each other.
|
|
574
|
-
*
|
|
575
|
-
* 3. Resilience: If Discord disconnects, the broadcast continues. Web listeners are
|
|
576
|
-
* unaffected. Discord can reconnect and resume from live position.
|
|
577
|
-
*
|
|
578
|
-
* 4. No Radio Dependency: music-player + discord work together without needing the
|
|
579
|
-
* radio plugin. Radio can add orchestration features, but isn't required.
|
|
580
|
-
*/
|
|
581
|
-
/**
|
|
582
|
-
* Subscription to an audio broadcast stream
|
|
583
|
-
*
|
|
584
|
-
* WHY: Each consumer needs independent control over their subscription.
|
|
585
|
-
* One consumer unsubscribing shouldn't affect others.
|
|
586
|
-
*/
|
|
587
|
-
interface AudioSubscription {
|
|
588
|
-
/** Unique identifier for this subscription */
|
|
589
|
-
readonly consumerId: string;
|
|
590
|
-
/** The audio stream (PCM or Opus frames) */
|
|
591
|
-
readonly stream: Readable;
|
|
592
|
-
/** Unsubscribe from the broadcast */
|
|
593
|
-
unsubscribe(): void;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Audio broadcast state
|
|
597
|
-
*/
|
|
598
|
-
type BroadcastState = "live" | "silence" | "stopped";
|
|
599
|
-
/**
|
|
600
|
-
* Track metadata for broadcast events
|
|
601
|
-
*/
|
|
602
|
-
interface BroadcastTrackMetadata {
|
|
603
|
-
title: string;
|
|
604
|
-
url: string;
|
|
605
|
-
duration?: number;
|
|
606
|
-
requestedBy?: string;
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Audio broadcast interface - represents a continuous audio stream
|
|
610
|
-
* that multiple consumers can subscribe to independently.
|
|
611
|
-
*
|
|
612
|
-
* WHY NON-BLOCKING:
|
|
613
|
-
* A slow web listener with a bad connection should NOT cause Discord audio to stutter.
|
|
614
|
-
* Each consumer is isolated - if they can't keep up, we drop their frames, not the source.
|
|
615
|
-
*
|
|
616
|
-
* WHY RESILIENT:
|
|
617
|
-
* Radio stations don't stop broadcasting when everyone changes the channel. Similarly,
|
|
618
|
-
* the broadcast continues even with zero subscribers, so reconnecting is seamless.
|
|
619
|
-
*
|
|
620
|
-
* WHY LIVE:
|
|
621
|
-
* When Discord reconnects after a hiccup, it should hear what's playing NOW, not
|
|
622
|
-
* buffered audio from 10 seconds ago. This is radio-style, not recording-style.
|
|
623
|
-
*
|
|
624
|
-
* Implementations must ensure:
|
|
625
|
-
* - Non-blocking: slow consumers don't affect the broadcast or other consumers
|
|
626
|
-
* - Resilient: broadcast continues even if all consumers disconnect
|
|
627
|
-
* - Live: new subscribers get current audio, not buffered past audio
|
|
628
|
-
*/
|
|
629
|
-
interface IAudioBroadcast extends EventEmitter {
|
|
630
|
-
/** Guild/server this broadcast is for */
|
|
631
|
-
readonly guildId: string;
|
|
632
|
-
/** Current broadcast state */
|
|
633
|
-
readonly state: BroadcastState;
|
|
634
|
-
/**
|
|
635
|
-
* Subscribe to this broadcast stream
|
|
636
|
-
* @param consumerId Unique identifier for the consumer
|
|
637
|
-
* @returns Subscription object with stream and unsubscribe method
|
|
638
|
-
*/
|
|
639
|
-
subscribe(consumerId: string): AudioSubscription;
|
|
640
|
-
/**
|
|
641
|
-
* Get current subscriber count
|
|
642
|
-
*/
|
|
643
|
-
getSubscriberCount(): number;
|
|
644
|
-
/**
|
|
645
|
-
* Check if a consumer is currently subscribed
|
|
646
|
-
*/
|
|
647
|
-
isSubscribed(consumerId: string): boolean;
|
|
648
|
-
on(event: "stateChange", listener: (state: BroadcastState) => void): this;
|
|
649
|
-
on(event: "metadata", listener: (metadata: BroadcastTrackMetadata) => void): this;
|
|
650
|
-
on(event: "subscriberAdded", listener: (consumerId: string) => void): this;
|
|
651
|
-
on(event: "subscriberRemoved", listener: (consumerId: string) => void): this;
|
|
652
|
-
emit(event: "stateChange", state: BroadcastState): boolean;
|
|
653
|
-
emit(event: "metadata", metadata: BroadcastTrackMetadata): boolean;
|
|
654
|
-
emit(event: "subscriberAdded", consumerId: string): boolean;
|
|
655
|
-
emit(event: "subscriberRemoved", consumerId: string): boolean;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Broadcast - Implements IAudioBroadcast contract
|
|
660
|
-
*
|
|
661
|
-
* WHY THIS FACADE:
|
|
662
|
-
* Consumers (Discord, Web) shouldn't care about internal complexity (StreamCore,
|
|
663
|
-
* StreamMultiplexer, silence generation, backpressure policies). They just want:
|
|
664
|
-
* - subscribe() → get audio stream
|
|
665
|
-
* - unsubscribe() → stop receiving audio
|
|
666
|
-
* - events → know what's playing
|
|
667
|
-
*
|
|
668
|
-
* This class is the ONLY public interface. Everything else (core components) is internal.
|
|
669
|
-
*
|
|
670
|
-
* ARCHITECTURE LAYERS:
|
|
671
|
-
* ```
|
|
672
|
-
* Consumer Layer: Discord, Web, etc. → call subscribe()
|
|
673
|
-
* ↓
|
|
674
|
-
* Facade Layer: Broadcast (this class) → implements IAudioBroadcast
|
|
675
|
-
* ↓
|
|
676
|
-
* Generation Layer: StreamCore → produces audio (tracks or silence)
|
|
677
|
-
* ↓
|
|
678
|
-
* Distribution Layer: StreamMultiplexer → fans out to N consumers
|
|
679
|
-
* ```
|
|
680
|
-
*
|
|
681
|
-
* WHY WIRE THEM IN CONSTRUCTOR:
|
|
682
|
-
* StreamCore → StreamMultiplexer wiring happens once. The output stream is long-lived
|
|
683
|
-
* (never closes). Tracks come and go, but the pipeline stays connected.
|
|
684
|
-
*
|
|
685
|
-
* This is the public interface that consumers (Discord, Web, etc.) interact with.
|
|
686
|
-
* Internally uses StreamCore for audio generation and StreamMultiplexer for fan-out.
|
|
687
|
-
*/
|
|
688
|
-
declare class Broadcast extends EventEmitter implements IAudioBroadcast {
|
|
689
|
-
readonly guildId: string;
|
|
690
|
-
private streamCore;
|
|
691
|
-
private multiplexer;
|
|
692
|
-
private _state;
|
|
693
|
-
constructor(guildId: string);
|
|
694
|
-
/**
|
|
695
|
-
* Get current broadcast state
|
|
696
|
-
*/
|
|
697
|
-
get state(): BroadcastState;
|
|
698
|
-
/**
|
|
699
|
-
* Subscribe to this broadcast
|
|
700
|
-
* @param consumerId Unique identifier for the consumer
|
|
701
|
-
* @returns AudioSubscription with stream and unsubscribe method
|
|
702
|
-
*/
|
|
703
|
-
subscribe(consumerId: string): AudioSubscription;
|
|
704
|
-
/**
|
|
705
|
-
* Get current subscriber count
|
|
706
|
-
*/
|
|
707
|
-
getSubscriberCount(): number;
|
|
708
|
-
/**
|
|
709
|
-
* Check if a consumer is subscribed
|
|
710
|
-
*/
|
|
711
|
-
isSubscribed(consumerId: string): boolean;
|
|
712
|
-
/**
|
|
713
|
-
* Push a track stream into the broadcast (internal API for MusicQueue)
|
|
714
|
-
* @param stream Audio stream
|
|
715
|
-
* @param metadata Track metadata
|
|
716
|
-
*/
|
|
717
|
-
pushTrack(stream: Readable, metadata: BroadcastTrackMetadata): Promise<void>;
|
|
718
|
-
/**
|
|
719
|
-
* Start silence generation (internal API)
|
|
720
|
-
*/
|
|
721
|
-
startSilence(): void;
|
|
722
|
-
/**
|
|
723
|
-
* Stop the broadcast (internal API)
|
|
724
|
-
*/
|
|
725
|
-
stop(): void;
|
|
726
|
-
/**
|
|
727
|
-
* Destroy the broadcast and clean up resources
|
|
728
|
-
*/
|
|
729
|
-
destroy(): void;
|
|
730
|
-
/**
|
|
731
|
-
* Get statistics about the broadcast
|
|
732
|
-
*/
|
|
733
|
-
getStats(): {
|
|
734
|
-
state: BroadcastState;
|
|
735
|
-
subscriberCount: number;
|
|
736
|
-
consumerStats: Map<string, {
|
|
737
|
-
droppedFrames: number;
|
|
738
|
-
totalFrames: number;
|
|
739
|
-
}>;
|
|
740
|
-
};
|
|
741
|
-
/**
|
|
742
|
-
* Map StreamCore state to BroadcastState
|
|
743
|
-
*/
|
|
744
|
-
private mapCoreStateToBroadcastState;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
interface AudioCacheKey {
|
|
748
|
-
artist?: string;
|
|
749
|
-
album?: string;
|
|
750
|
-
song: string;
|
|
751
|
-
quality: "low" | "medium" | "high" | "highest";
|
|
752
|
-
url: string;
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Audio cache service that downloads, converts, and caches audio files
|
|
756
|
-
*
|
|
757
|
-
* ## Overview
|
|
758
|
-
* This service manages a file-based cache of audio files downloaded from YouTube.
|
|
759
|
-
* Audio is downloaded using yt-dlp and converted to OGG Opus format, which is:
|
|
760
|
-
* - Native to Discord voice (no transcoding needed)
|
|
761
|
-
* - Efficiently compressed (smaller cache size)
|
|
762
|
-
* - High quality (VBR encoding)
|
|
763
|
-
* - Web-compatible (supported by modern browsers)
|
|
764
|
-
*
|
|
765
|
-
* ## Caching Strategy
|
|
766
|
-
* Files are cached based on artist/album/song/quality and stored in the configured
|
|
767
|
-
* cache directory. Each cached file includes metadata (duration, bitrate, etc.)
|
|
768
|
-
* obtained via ffprobe.
|
|
769
|
-
*
|
|
770
|
-
* ## Stream Handling (CRITICAL)
|
|
771
|
-
* ⚠️ This service creates clean, unmodified file streams. DO NOT add event listeners
|
|
772
|
-
* or call resume() on these streams - adding listeners (especially 'readable' and 'data')
|
|
773
|
-
* puts streams into paused mode and prevents Discord.js from controlling stream flow.
|
|
774
|
-
*
|
|
775
|
-
* Let the consumer (Discord.js) handle all stream control, probing, and consumption.
|
|
776
|
-
*
|
|
777
|
-
* @example
|
|
778
|
-
* ```typescript
|
|
779
|
-
* const cache = new AudioCacheService('/path/to/cache');
|
|
780
|
-
* const key = { song: 'Track Name', quality: 'high', url: 'https://...' };
|
|
781
|
-
* const stream = await cache.getAudioStream(key, 'https://youtube.com/...');
|
|
782
|
-
* // Pass stream directly to Discord.js - don't touch it!
|
|
783
|
-
* voiceManager.playAudio(stream, { guildId, channel });
|
|
784
|
-
* ```
|
|
785
|
-
*/
|
|
786
|
-
declare class AudioCacheService {
|
|
787
|
-
private cacheDir;
|
|
788
|
-
private readonly CACHE_TTL;
|
|
789
|
-
private cacheIndex;
|
|
790
|
-
constructor(cacheDir?: string);
|
|
791
|
-
/**
|
|
792
|
-
* Generate cache key from track metadata
|
|
793
|
-
*/
|
|
794
|
-
private generateCacheKey;
|
|
795
|
-
/**
|
|
796
|
-
* Simple hash function for URLs
|
|
797
|
-
*/
|
|
798
|
-
private hashString;
|
|
799
|
-
/**
|
|
800
|
-
* Extract YouTube video ID from various URL formats
|
|
801
|
-
* WHY: YouTube URLs can come in many forms but point to the same video:
|
|
802
|
-
* - https://www.youtube.com/watch?v=ABC123
|
|
803
|
-
* - https://youtube.com/watch?v=ABC123
|
|
804
|
-
* - https://youtu.be/ABC123
|
|
805
|
-
* - https://www.youtube.com/watch?v=ABC123&list=PLxyz&index=1
|
|
806
|
-
*
|
|
807
|
-
* We need to normalize these to get consistent cache keys
|
|
808
|
-
*/
|
|
809
|
-
private extractYouTubeVideoId;
|
|
810
|
-
/**
|
|
811
|
-
* Normalize URL for consistent caching
|
|
812
|
-
* WHY: Same video can have different URL formats - we want them to hit the same cache entry
|
|
813
|
-
*/
|
|
814
|
-
private normalizeUrlForCache;
|
|
815
|
-
/**
|
|
816
|
-
* Get cache file path for a key
|
|
817
|
-
*/
|
|
818
|
-
private getCacheFilePath;
|
|
819
|
-
/**
|
|
820
|
-
* Check if audio is cached (checks all supported formats)
|
|
821
|
-
*/
|
|
822
|
-
isCached(key: AudioCacheKey): boolean;
|
|
823
|
-
/**
|
|
824
|
-
* Find cached audio by URL (searches all cached files)
|
|
825
|
-
* This is useful when you only have the URL and don't know the exact cache key
|
|
826
|
-
*/
|
|
827
|
-
findCachedAudioByUrl(url: string, quality?: "low" | "medium" | "high" | "highest"): Promise<Readable | null>;
|
|
828
|
-
/**
|
|
829
|
-
* Get cached audio file as a readable stream
|
|
830
|
-
*
|
|
831
|
-
* Returns a clean file stream with NO event listeners attached (except error handling).
|
|
832
|
-
* The stream is in paused mode by default - the consumer must control flow.
|
|
833
|
-
*
|
|
834
|
-
* ⚠️ WARNING: Do not add event listeners or call resume() on the returned stream.
|
|
835
|
-
* Adding listeners puts the stream in paused mode and prevents proper playback.
|
|
836
|
-
* Let Discord.js handle all stream control.
|
|
837
|
-
*
|
|
838
|
-
* @param key - Cache key identifying the audio file
|
|
839
|
-
* @returns Clean readable stream, or null if not cached/invalid
|
|
840
|
-
*/
|
|
841
|
-
getCachedAudio(key: AudioCacheKey): Promise<Readable | null>;
|
|
842
|
-
/**
|
|
843
|
-
* Download and cache audio from YouTube URL
|
|
844
|
-
*
|
|
845
|
-
* Uses yt-dlp to download audio and convert to the configured format.
|
|
846
|
-
* yt-dlp automatically invokes ffmpeg for format conversion.
|
|
847
|
-
*
|
|
848
|
-
* ## Process
|
|
849
|
-
* 1. Check if already cached (skip if so)
|
|
850
|
-
* 2. Download audio using yt-dlp with quality settings
|
|
851
|
-
* 3. Convert to configured format (default: Opus, configurable via AUDIO_CACHE_FORMAT)
|
|
852
|
-
* 4. Probe metadata with ffprobe (duration, bitrate, etc.)
|
|
853
|
-
* 5. Update cache index
|
|
854
|
-
*
|
|
855
|
-
* ## Supported Formats
|
|
856
|
-
* - opus: Discord-optimized, lossy but high quality (default)
|
|
857
|
-
* - flac: Lossless format, prevents additional quality loss from lossy-to-lossy conversions (larger files)
|
|
858
|
-
* Even though YouTube source is lossy, FLAC preserves the best quality available and prevents
|
|
859
|
-
* further degradation from multiple lossy conversions (e.g., Opus→Opus or Opus→MP3)
|
|
860
|
-
* - wav: Uncompressed lossless (very large files)
|
|
861
|
-
* - mp3: Lossy, widely compatible
|
|
862
|
-
* - m4a: Lossy, Apple format
|
|
863
|
-
*
|
|
864
|
-
* Set AUDIO_CACHE_FORMAT environment variable to choose format (e.g., export AUDIO_CACHE_FORMAT=flac)
|
|
865
|
-
*
|
|
866
|
-
* Note: Using FLAC prevents quality loss from lossy-to-lossy transcoding, preserving the best
|
|
867
|
-
* quality available from the YouTube source even if the source itself is lossy.
|
|
868
|
-
*
|
|
869
|
-
* ## Quality Mapping
|
|
870
|
-
* - low: worst available audio
|
|
871
|
-
* - medium: up to 128kbps
|
|
872
|
-
* - high: up to 192kbps
|
|
873
|
-
* - highest: best available audio
|
|
874
|
-
*
|
|
875
|
-
* Note: FLAC is lossless, so quality settings don't apply (preserves source quality)
|
|
876
|
-
*
|
|
877
|
-
* @param key - Cache key for the audio
|
|
878
|
-
* @param youtubeUrl - YouTube URL to download from
|
|
879
|
-
* @returns Path to cached file
|
|
880
|
-
* @throws Error if yt-dlp/ffmpeg not installed, download fails, or file invalid
|
|
881
|
-
*/
|
|
882
|
-
downloadAndCache(key: AudioCacheKey, youtubeUrl: string): Promise<string>;
|
|
883
|
-
/**
|
|
884
|
-
* Get or download audio (returns file path)
|
|
885
|
-
*/
|
|
886
|
-
getOrDownload(key: AudioCacheKey, youtubeUrl: string): Promise<string>;
|
|
887
|
-
/**
|
|
888
|
-
* Resolve the on-disk file path for a cached track by URL (any quality/format).
|
|
889
|
-
* Returns null when the file has not been cached yet or is expired.
|
|
890
|
-
*/
|
|
891
|
-
findCachedFilePathByUrl(url: string): string | null;
|
|
892
|
-
/**
|
|
893
|
-
* Get cached audio as stream, or download and return stream
|
|
894
|
-
*/
|
|
895
|
-
getAudioStream(key: AudioCacheKey, youtubeUrl: string): Promise<Readable>;
|
|
896
|
-
/**
|
|
897
|
-
* Probe audio file using ffprobe to get metadata
|
|
898
|
-
*/
|
|
899
|
-
private probeAudioFile;
|
|
900
|
-
/**
|
|
901
|
-
* Load cache index from filesystem
|
|
902
|
-
*/
|
|
903
|
-
private loadCacheIndex;
|
|
904
|
-
/**
|
|
905
|
-
* Clean up expired cache entries
|
|
906
|
-
*/
|
|
907
|
-
private cleanExpiredCache;
|
|
908
|
-
/**
|
|
909
|
-
* Get cache statistics
|
|
910
|
-
*/
|
|
911
|
-
getCacheStats(): {
|
|
912
|
-
totalFiles: number;
|
|
913
|
-
totalSize: number;
|
|
914
|
-
oldestEntry: number | null;
|
|
915
|
-
newestEntry: number | null;
|
|
916
|
-
};
|
|
917
|
-
/**
|
|
918
|
-
* Clear all cache
|
|
919
|
-
*/
|
|
920
|
-
clearCache(): void;
|
|
921
|
-
/**
|
|
922
|
-
* Get audio metadata for a cached file (returns undefined if not cached)
|
|
923
|
-
*/
|
|
924
|
-
getAudioMetadata(key: AudioCacheKey): Promise<{
|
|
925
|
-
bitrate?: number;
|
|
926
|
-
format?: string;
|
|
927
|
-
sampleRate?: number;
|
|
928
|
-
channels?: number;
|
|
929
|
-
size?: number;
|
|
930
|
-
duration?: number;
|
|
931
|
-
} | undefined>;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
interface VoicePlaybackHandle {
|
|
935
|
-
finished?: Promise<void>;
|
|
936
|
-
}
|
|
937
|
-
/** Minimal voice pipeline from plugin-discord (avoids hard dependency here). */
|
|
938
|
-
interface VoiceManagerLike {
|
|
939
|
-
stopAudio(guildId: string, channel: number): Promise<void>;
|
|
940
|
-
pauseAudio(guildId: string, channel: number): Promise<void>;
|
|
941
|
-
resumeAudio(guildId: string, channel: number): Promise<void>;
|
|
942
|
-
isPlaying(guildId: string, channel: number): Promise<boolean>;
|
|
943
|
-
on(event: "audio:finished", handler: (data: {
|
|
944
|
-
guildId: string;
|
|
945
|
-
channel: number;
|
|
946
|
-
}) => void): void;
|
|
947
|
-
off(event: "audio:finished", handler: (data: {
|
|
948
|
-
guildId: string;
|
|
949
|
-
channel: number;
|
|
950
|
-
}) => void): void;
|
|
951
|
-
playAudio(stream: Readable, options: {
|
|
952
|
-
guildId: string;
|
|
953
|
-
channel: number;
|
|
954
|
-
volume?: number;
|
|
955
|
-
metadata?: Record<string, unknown>;
|
|
956
|
-
}): Promise<VoicePlaybackHandle>;
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Represents a track in the music queue
|
|
960
|
-
*/
|
|
961
|
-
interface QueuedTrack {
|
|
962
|
-
id: string;
|
|
963
|
-
url: string;
|
|
964
|
-
title: string;
|
|
965
|
-
duration?: number;
|
|
966
|
-
requestedBy?: string;
|
|
967
|
-
dedicatedTo?: string;
|
|
968
|
-
dedicationMessage?: string;
|
|
969
|
-
addedAt: number;
|
|
970
|
-
stream?: Readable;
|
|
971
|
-
audioMetadata?: {
|
|
972
|
-
bitrate?: number;
|
|
973
|
-
format?: string;
|
|
974
|
-
sampleRate?: number;
|
|
975
|
-
channels?: number;
|
|
976
|
-
size?: number;
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Options for cross-fading between tracks
|
|
981
|
-
*/
|
|
982
|
-
interface CrossFadeOptions {
|
|
983
|
-
enabled: boolean;
|
|
984
|
-
duration: number;
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Music queue manager with cross-fading support
|
|
988
|
-
*/
|
|
989
|
-
declare class MusicQueue extends EventEmitter {
|
|
990
|
-
private queue;
|
|
991
|
-
private currentTrack;
|
|
992
|
-
private currentPlaybackHandle;
|
|
993
|
-
private isPlaying;
|
|
994
|
-
private isPaused;
|
|
995
|
-
private guildId;
|
|
996
|
-
private voiceManager;
|
|
997
|
-
private crossFadeOptions;
|
|
998
|
-
private nextTrackPreBuffered;
|
|
999
|
-
private readonly MUSIC_CHANNEL;
|
|
1000
|
-
private continuousStream;
|
|
1001
|
-
private nextTrackStream;
|
|
1002
|
-
private preBufferTimeout;
|
|
1003
|
-
private trackPlayedCallback;
|
|
1004
|
-
private audioCache;
|
|
1005
|
-
private runtime;
|
|
1006
|
-
private broadcast;
|
|
1007
|
-
constructor(guildId: string, voiceManager: VoiceManagerLike | null, runtime?: IAgentRuntime, audioCache?: AudioCacheService);
|
|
1008
|
-
/**
|
|
1009
|
-
* Set the broadcast for this queue (new architecture)
|
|
1010
|
-
* @param broadcast Broadcast instance to push audio to
|
|
1011
|
-
*/
|
|
1012
|
-
setBroadcast(broadcast: Broadcast): void;
|
|
1013
|
-
/**
|
|
1014
|
-
* Register callback for when a track finishes playing
|
|
1015
|
-
*/
|
|
1016
|
-
onTrackPlayed(callback: (track: QueuedTrack, duration: number) => Promise<void>): void;
|
|
1017
|
-
/**
|
|
1018
|
-
* Set cross-fade options
|
|
1019
|
-
*/
|
|
1020
|
-
setCrossFadeOptions(options: Partial<CrossFadeOptions>): void;
|
|
1021
|
-
/**
|
|
1022
|
-
* Add a track to the queue
|
|
1023
|
-
*/
|
|
1024
|
-
addTrack(track: Omit<QueuedTrack, "id" | "addedAt">): Promise<QueuedTrack>;
|
|
1025
|
-
/**
|
|
1026
|
-
* Remove a track from the queue
|
|
1027
|
-
*/
|
|
1028
|
-
removeTrack(trackId: string): boolean;
|
|
1029
|
-
/**
|
|
1030
|
-
* Skip the current track.
|
|
1031
|
-
* Works for both legacy voiceManager and broadcast architectures.
|
|
1032
|
-
*/
|
|
1033
|
-
skip(): Promise<boolean>;
|
|
1034
|
-
/**
|
|
1035
|
-
* Pause playback.
|
|
1036
|
-
* For Discord voice: delegates to voiceManager.pauseAudio.
|
|
1037
|
-
* For broadcast (web): stops the broadcast StreamCore so HTTP clients
|
|
1038
|
-
* stop receiving new audio frames (they stay connected but hear silence).
|
|
1039
|
-
*/
|
|
1040
|
-
pause(): Promise<void>;
|
|
1041
|
-
/**
|
|
1042
|
-
* Resume playback.
|
|
1043
|
-
* For Discord voice: delegates to voiceManager.resumeAudio.
|
|
1044
|
-
* For broadcast (web): re-pushes the current track metadata so clients
|
|
1045
|
-
* know playback resumed. The actual audio stream is still wired; calling
|
|
1046
|
-
* broadcast.startSilence() unblocks the pipeline and the next pushTrack
|
|
1047
|
-
* will pick it back up.
|
|
1048
|
-
*/
|
|
1049
|
-
resume(): Promise<void>;
|
|
1050
|
-
/**
|
|
1051
|
-
* Stop playback and clear queue.
|
|
1052
|
-
* Stops both legacy voiceManager and broadcast paths so all consumers
|
|
1053
|
-
* (Discord voice + HTTP stream clients) stop receiving audio.
|
|
1054
|
-
*/
|
|
1055
|
-
stop(): Promise<void>;
|
|
1056
|
-
/**
|
|
1057
|
-
* Clear the queue
|
|
1058
|
-
*/
|
|
1059
|
-
clear(): void;
|
|
1060
|
-
/**
|
|
1061
|
-
* Shuffle the queue
|
|
1062
|
-
*/
|
|
1063
|
-
shuffle(): void;
|
|
1064
|
-
/**
|
|
1065
|
-
* Get the queue
|
|
1066
|
-
*/
|
|
1067
|
-
getQueue(): QueuedTrack[];
|
|
1068
|
-
/**
|
|
1069
|
-
* Get the current track
|
|
1070
|
-
*/
|
|
1071
|
-
getCurrentTrack(): QueuedTrack | null;
|
|
1072
|
-
/**
|
|
1073
|
-
* Get whether playback is active
|
|
1074
|
-
*/
|
|
1075
|
-
getIsPlaying(): boolean;
|
|
1076
|
-
/**
|
|
1077
|
-
* Get whether playback is paused
|
|
1078
|
-
*/
|
|
1079
|
-
getIsPaused(): boolean;
|
|
1080
|
-
/**
|
|
1081
|
-
* Get the continuous stream
|
|
1082
|
-
*/
|
|
1083
|
-
getContinuousStream(): PassThrough | null;
|
|
1084
|
-
/**
|
|
1085
|
-
* Get audio stream for a track (cached or download)
|
|
1086
|
-
* Tries cache first (title-based, then URL-based), then downloads if not cached.
|
|
1087
|
-
* @param track - Track to get stream for
|
|
1088
|
-
* @param shouldCache - Whether to cache downloaded streams (default: true)
|
|
1089
|
-
* @returns Audio stream or null if failed
|
|
1090
|
-
*/
|
|
1091
|
-
private getAudioStreamForTrack;
|
|
1092
|
-
/**
|
|
1093
|
-
* Clean up pre-buffered stream
|
|
1094
|
-
*/
|
|
1095
|
-
private cleanupPreBufferedStream;
|
|
1096
|
-
/**
|
|
1097
|
-
* Pre-buffer the next track for cross-fading
|
|
1098
|
-
*/
|
|
1099
|
-
private preBufferNextTrack;
|
|
1100
|
-
/**
|
|
1101
|
-
* Play the next track in the queue
|
|
1102
|
-
*/
|
|
1103
|
-
private playNext;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Compatibility fallback for `/music-player/status`.
|
|
1108
|
-
*
|
|
1109
|
-
* The UI polls this route even when plugin-music-player is not enabled. When the
|
|
1110
|
-
* plugin route is absent, returning a stable JSON payload is better than a noisy
|
|
1111
|
-
* 404 loop in desktop logs.
|
|
1112
|
-
*/
|
|
1113
|
-
declare function tryHandleMusicPlayerStatusFallback(options: {
|
|
1114
|
-
pathname: string;
|
|
1115
|
-
method: string;
|
|
1116
|
-
runtime: AgentRuntime | null | undefined;
|
|
1117
|
-
res: ServerResponse;
|
|
1118
|
-
}): boolean;
|
|
1119
|
-
|
|
1120
|
-
/** Minimal VoiceTarget shape — avoids hard dep on @elizaos/plugin-discord. */
|
|
1121
|
-
interface VoiceTarget {
|
|
1122
|
-
id: string;
|
|
1123
|
-
type: string;
|
|
1124
|
-
guildId?: string;
|
|
1125
|
-
channelId?: string;
|
|
1126
|
-
play?: (stream: Readable, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
1127
|
-
playAudio?: (stream: Readable, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
1128
|
-
feed?: (stream: Readable) => Promise<unknown>;
|
|
1129
|
-
stop?: () => Promise<unknown>;
|
|
1130
|
-
stopAudio?: () => Promise<unknown>;
|
|
1131
|
-
[key: string]: unknown;
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Audio routing mode
|
|
1135
|
-
*/
|
|
1136
|
-
type AudioRoutingMode = "simulcast" | "independent";
|
|
1137
|
-
/**
|
|
1138
|
-
* Configuration for audio routing
|
|
1139
|
-
*/
|
|
1140
|
-
interface AudioRouteConfig {
|
|
1141
|
-
sourceId: string;
|
|
1142
|
-
targetIds: string[];
|
|
1143
|
-
mode?: AudioRoutingMode;
|
|
1144
|
-
}
|
|
1145
|
-
/**
|
|
1146
|
-
* AudioRouter manages routing of audio streams to multiple voice targets
|
|
1147
|
-
* Supports both simulcast (same stream to all) and independent (separate streams) modes
|
|
1148
|
-
*/
|
|
1149
|
-
declare class AudioRouter {
|
|
1150
|
-
private routes;
|
|
1151
|
-
private defaultMode;
|
|
1152
|
-
private targetRegistry;
|
|
1153
|
-
constructor(defaultMode?: AudioRoutingMode);
|
|
1154
|
-
/**
|
|
1155
|
-
* Register voice targets for routing
|
|
1156
|
-
*/
|
|
1157
|
-
registerTargets(targets: VoiceTarget[]): void;
|
|
1158
|
-
/**
|
|
1159
|
-
* Unregister voice targets
|
|
1160
|
-
*/
|
|
1161
|
-
unregisterTarget(targetId: string): void;
|
|
1162
|
-
/**
|
|
1163
|
-
* Get registered target by ID
|
|
1164
|
-
*/
|
|
1165
|
-
getTarget(targetId: string): VoiceTarget | undefined;
|
|
1166
|
-
/**
|
|
1167
|
-
* Route an audio stream to multiple targets
|
|
1168
|
-
* @param sourceId Unique identifier for the audio source
|
|
1169
|
-
* @param stream The audio stream to route
|
|
1170
|
-
* @param targetIds Array of target IDs to route to
|
|
1171
|
-
* @param mode Routing mode (defaults to configured default)
|
|
1172
|
-
*/
|
|
1173
|
-
route(sourceId: string, stream: Readable, targetIds: string[], mode?: AudioRoutingMode, onCleanup?: () => void): Promise<void>;
|
|
1174
|
-
/**
|
|
1175
|
-
* Simulcast mode: Clone single stream to all targets
|
|
1176
|
-
* Uses PassThrough streams to multiplex
|
|
1177
|
-
*/
|
|
1178
|
-
private routeSimulcast;
|
|
1179
|
-
/**
|
|
1180
|
-
* Independent mode: Separate streams per target
|
|
1181
|
-
* Note: This requires the source to provide multiple independent streams
|
|
1182
|
-
* For now, this will use the same stream but track separately
|
|
1183
|
-
*/
|
|
1184
|
-
private routeIndependent;
|
|
1185
|
-
/**
|
|
1186
|
-
* Stop routing for a source
|
|
1187
|
-
*/
|
|
1188
|
-
unroute(sourceId: string): Promise<void>;
|
|
1189
|
-
/**
|
|
1190
|
-
* Stop all active routes
|
|
1191
|
-
*/
|
|
1192
|
-
unrouteAll(): Promise<void>;
|
|
1193
|
-
/**
|
|
1194
|
-
* Get active routes
|
|
1195
|
-
*/
|
|
1196
|
-
getActiveRoutes(): Array<{
|
|
1197
|
-
sourceId: string;
|
|
1198
|
-
targetIds: string[];
|
|
1199
|
-
mode: AudioRoutingMode;
|
|
1200
|
-
}>;
|
|
1201
|
-
/**
|
|
1202
|
-
* Set default routing mode
|
|
1203
|
-
*/
|
|
1204
|
-
setDefaultMode(mode: AudioRoutingMode): void;
|
|
1205
|
-
/**
|
|
1206
|
-
* Get default routing mode
|
|
1207
|
-
*/
|
|
1208
|
-
getDefaultMode(): AudioRoutingMode;
|
|
1209
|
-
/**
|
|
1210
|
-
* Get all registered routing target IDs
|
|
1211
|
-
*/
|
|
1212
|
-
getRegisteredTargetIds(): string[];
|
|
1213
|
-
/**
|
|
1214
|
-
* Check if a source is currently routed
|
|
1215
|
-
*/
|
|
1216
|
-
isRouted(sourceId: string): boolean;
|
|
1217
|
-
/**
|
|
1218
|
-
* Get route info for a source
|
|
1219
|
-
*/
|
|
1220
|
-
getRoute(sourceId: string): {
|
|
1221
|
-
sourceId: string;
|
|
1222
|
-
targetIds: string[];
|
|
1223
|
-
mode: AudioRoutingMode;
|
|
1224
|
-
} | undefined;
|
|
1225
|
-
/**
|
|
1226
|
-
* Add target to existing route
|
|
1227
|
-
*/
|
|
1228
|
-
addTargetToRoute(sourceId: string, targetId: string): Promise<void>;
|
|
1229
|
-
/**
|
|
1230
|
-
* Remove target from existing route
|
|
1231
|
-
*/
|
|
1232
|
-
removeTargetFromRoute(sourceId: string, targetId: string): Promise<void>;
|
|
1233
|
-
private startTargetPlayback;
|
|
1234
|
-
private stopTargetPlayback;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
/**
|
|
1238
|
-
* Configuration for a mix session
|
|
1239
|
-
*/
|
|
1240
|
-
interface MixConfig {
|
|
1241
|
-
name: string;
|
|
1242
|
-
zones: Record<string, string[]>;
|
|
1243
|
-
routing?: {
|
|
1244
|
-
mode: AudioRoutingMode;
|
|
1245
|
-
mappings: Record<string, string[]>;
|
|
1246
|
-
};
|
|
1247
|
-
autoCleanup?: boolean;
|
|
1248
|
-
cleanupDelay?: number;
|
|
1249
|
-
metadata?: Record<string, unknown>;
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Active mix session state
|
|
1253
|
-
*/
|
|
1254
|
-
interface MixSession {
|
|
1255
|
-
id: string;
|
|
1256
|
-
config: MixConfig;
|
|
1257
|
-
startedAt: number;
|
|
1258
|
-
cleanupTimeout?: NodeJS.Timeout;
|
|
1259
|
-
}
|
|
1260
|
-
/**
|
|
1261
|
-
* MixSessionManager handles runtime mixing configurations
|
|
1262
|
-
* Manages temporary bot assignments, routing, and auto-cleanup
|
|
1263
|
-
*/
|
|
1264
|
-
declare class MixSessionManager {
|
|
1265
|
-
private sessions;
|
|
1266
|
-
private runtime;
|
|
1267
|
-
constructor(runtime: IAgentRuntime);
|
|
1268
|
-
/**
|
|
1269
|
-
* Start a new mix session
|
|
1270
|
-
* @param config Mix configuration
|
|
1271
|
-
* @returns Created session
|
|
1272
|
-
*/
|
|
1273
|
-
start(config: MixConfig): Promise<MixSession>;
|
|
1274
|
-
/**
|
|
1275
|
-
* End a mix session
|
|
1276
|
-
* @param sessionId Session ID to end
|
|
1277
|
-
*/
|
|
1278
|
-
end(sessionId: string): Promise<void>;
|
|
1279
|
-
/**
|
|
1280
|
-
* Update an existing session
|
|
1281
|
-
* @param sessionId Session ID to update
|
|
1282
|
-
* @param config Partial configuration to update
|
|
1283
|
-
*/
|
|
1284
|
-
update(sessionId: string, config: Partial<MixConfig>): Promise<MixSession>;
|
|
1285
|
-
/**
|
|
1286
|
-
* Get a session by ID
|
|
1287
|
-
*/
|
|
1288
|
-
get(sessionId: string): MixSession | undefined;
|
|
1289
|
-
/**
|
|
1290
|
-
* Get all active sessions
|
|
1291
|
-
*/
|
|
1292
|
-
list(): MixSession[];
|
|
1293
|
-
/**
|
|
1294
|
-
* Find sessions by name
|
|
1295
|
-
*/
|
|
1296
|
-
findByName(name: string): MixSession[];
|
|
1297
|
-
/**
|
|
1298
|
-
* End all sessions
|
|
1299
|
-
*/
|
|
1300
|
-
endAll(): Promise<void>;
|
|
1301
|
-
/**
|
|
1302
|
-
* Check if a session exists
|
|
1303
|
-
*/
|
|
1304
|
-
exists(sessionId: string): boolean;
|
|
1305
|
-
/**
|
|
1306
|
-
* Get active session count
|
|
1307
|
-
*/
|
|
1308
|
-
count(): number;
|
|
1309
|
-
/**
|
|
1310
|
-
* Extend session cleanup timer
|
|
1311
|
-
* @param sessionId Session ID
|
|
1312
|
-
* @param additionalTime Additional time in milliseconds
|
|
1313
|
-
*/
|
|
1314
|
-
extendSession(sessionId: string, additionalTime: number): void;
|
|
1315
|
-
/**
|
|
1316
|
-
* Cancel auto-cleanup for a session
|
|
1317
|
-
*/
|
|
1318
|
-
cancelAutoCleanup(sessionId: string): void;
|
|
1319
|
-
/**
|
|
1320
|
-
* Save session to agent memory
|
|
1321
|
-
* @private
|
|
1322
|
-
*/
|
|
1323
|
-
private saveToMemory;
|
|
1324
|
-
/**
|
|
1325
|
-
* Remove session from agent memory
|
|
1326
|
-
* @private
|
|
1327
|
-
*/
|
|
1328
|
-
private removeFromMemory;
|
|
1329
|
-
/**
|
|
1330
|
-
* Load sessions from agent memory on startup
|
|
1331
|
-
*/
|
|
1332
|
-
loadFromMemory(): Promise<void>;
|
|
1333
|
-
/**
|
|
1334
|
-
* Get session duration in milliseconds
|
|
1335
|
-
*/
|
|
1336
|
-
getSessionDuration(sessionId: string): number;
|
|
1337
|
-
/**
|
|
1338
|
-
* Get session info summary
|
|
1339
|
-
*/
|
|
1340
|
-
getSessionInfo(sessionId: string): {
|
|
1341
|
-
id: string;
|
|
1342
|
-
name: string;
|
|
1343
|
-
duration: number;
|
|
1344
|
-
zoneCount: number;
|
|
1345
|
-
hasAutoCleanup: boolean;
|
|
1346
|
-
} | undefined;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
* A named group of voice targets
|
|
1351
|
-
*/
|
|
1352
|
-
interface Zone {
|
|
1353
|
-
name: string;
|
|
1354
|
-
targetIds: string[];
|
|
1355
|
-
createdAt: number;
|
|
1356
|
-
metadata?: Record<string, unknown>;
|
|
1357
|
-
}
|
|
1358
|
-
/**
|
|
1359
|
-
* ZoneManager manages logical groupings of voice targets
|
|
1360
|
-
* Zones allow routing audio to named collections of targets
|
|
1361
|
-
*/
|
|
1362
|
-
declare class ZoneManager {
|
|
1363
|
-
private zones;
|
|
1364
|
-
/**
|
|
1365
|
-
* Create a new zone
|
|
1366
|
-
* @param name Zone name
|
|
1367
|
-
* @param targetIds Array of target IDs in this zone
|
|
1368
|
-
* @param metadata Optional metadata for the zone
|
|
1369
|
-
*/
|
|
1370
|
-
create(name: string, targetIds: string[], metadata?: Record<string, unknown>): Zone;
|
|
1371
|
-
/**
|
|
1372
|
-
* Delete a zone
|
|
1373
|
-
*/
|
|
1374
|
-
delete(name: string): boolean;
|
|
1375
|
-
/**
|
|
1376
|
-
* Get a zone by name
|
|
1377
|
-
*/
|
|
1378
|
-
get(name: string): Zone | undefined;
|
|
1379
|
-
/**
|
|
1380
|
-
* Get all zones
|
|
1381
|
-
*/
|
|
1382
|
-
list(): Zone[];
|
|
1383
|
-
/**
|
|
1384
|
-
* Check if a zone exists
|
|
1385
|
-
*/
|
|
1386
|
-
exists(name: string): boolean;
|
|
1387
|
-
/**
|
|
1388
|
-
* Add target to a zone
|
|
1389
|
-
*/
|
|
1390
|
-
addTarget(zoneName: string, targetId: string): void;
|
|
1391
|
-
/**
|
|
1392
|
-
* Remove target from a zone
|
|
1393
|
-
*/
|
|
1394
|
-
removeTarget(zoneName: string, targetId: string): void;
|
|
1395
|
-
/**
|
|
1396
|
-
* Get all target IDs in a zone
|
|
1397
|
-
*/
|
|
1398
|
-
getTargets(zoneName: string): string[];
|
|
1399
|
-
/**
|
|
1400
|
-
* Update zone metadata
|
|
1401
|
-
*/
|
|
1402
|
-
updateMetadata(zoneName: string, metadata: Record<string, unknown>): void;
|
|
1403
|
-
/**
|
|
1404
|
-
* Find all zones containing a specific target
|
|
1405
|
-
*/
|
|
1406
|
-
findZonesWithTarget(targetId: string): Zone[];
|
|
1407
|
-
/**
|
|
1408
|
-
* Clear all zones
|
|
1409
|
-
*/
|
|
1410
|
-
clear(): void;
|
|
1411
|
-
/**
|
|
1412
|
-
* Get zone count
|
|
1413
|
-
*/
|
|
1414
|
-
count(): number;
|
|
1415
|
-
/**
|
|
1416
|
-
* Merge multiple zones into a new zone
|
|
1417
|
-
*/
|
|
1418
|
-
merge(newZoneName: string, zoneNames: string[]): Zone;
|
|
1419
|
-
/**
|
|
1420
|
-
* Clone a zone with a new name
|
|
1421
|
-
*/
|
|
1422
|
-
clone(sourceName: string, newName: string): Zone;
|
|
1423
|
-
/**
|
|
1424
|
-
* Get targets that are in multiple zones (intersection)
|
|
1425
|
-
*/
|
|
1426
|
-
getIntersection(zoneNames: string[]): string[];
|
|
1427
|
-
/**
|
|
1428
|
-
* Get all unique targets across multiple zones (union)
|
|
1429
|
-
*/
|
|
1430
|
-
getUnion(zoneNames: string[]): string[];
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
interface RoutingTarget {
|
|
1434
|
-
id: string;
|
|
1435
|
-
type: string;
|
|
1436
|
-
guildId?: string;
|
|
1437
|
-
channelId?: string;
|
|
1438
|
-
play?: (stream: NodeJS.ReadableStream, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
1439
|
-
playAudio?: (stream: NodeJS.ReadableStream, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
1440
|
-
feed?: (stream: NodeJS.ReadableStream) => Promise<unknown>;
|
|
1441
|
-
stop?: () => Promise<unknown>;
|
|
1442
|
-
stopAudio?: () => Promise<unknown>;
|
|
1443
|
-
[key: string]: unknown;
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Music service that manages music queues and playback
|
|
1447
|
-
* Pure playback engine - analytics/preferences are optional via music-library plugin
|
|
1448
|
-
*/
|
|
1449
|
-
declare class MusicService extends Service {
|
|
1450
|
-
static serviceType: string;
|
|
1451
|
-
capabilityDescription: string;
|
|
1452
|
-
private queues;
|
|
1453
|
-
private broadcasts;
|
|
1454
|
-
private voiceManager;
|
|
1455
|
-
private audioCache;
|
|
1456
|
-
private readonly audioRouter;
|
|
1457
|
-
private readonly zoneManager;
|
|
1458
|
-
constructor(runtime?: IAgentRuntime);
|
|
1459
|
-
static start(runtime: IAgentRuntime): Promise<MusicService>;
|
|
1460
|
-
stop(): Promise<void>;
|
|
1461
|
-
/**
|
|
1462
|
-
* Initialize the service with voice manager
|
|
1463
|
-
*/
|
|
1464
|
-
setVoiceManager(voiceManager: VoiceManagerLike): void;
|
|
1465
|
-
getAudioRouter(): AudioRouter;
|
|
1466
|
-
getZoneManager(): ZoneManager;
|
|
1467
|
-
setRoutingMode(mode: AudioRoutingMode): void;
|
|
1468
|
-
getRoutingMode(): AudioRoutingMode;
|
|
1469
|
-
registerRoutingTargets(targets: RoutingTarget[]): void;
|
|
1470
|
-
unregisterRoutingTarget(targetId: string): void;
|
|
1471
|
-
listRoutingTargets(): string[];
|
|
1472
|
-
startBroadcastRoute(sourceId: string, targetIds: string[], mode?: AudioRoutingMode): Promise<{
|
|
1473
|
-
sourceId: string;
|
|
1474
|
-
targetIds: string[];
|
|
1475
|
-
mode: AudioRoutingMode;
|
|
1476
|
-
}>;
|
|
1477
|
-
stopBroadcastRoute(sourceId: string): Promise<void>;
|
|
1478
|
-
getRoutingStatus(): {
|
|
1479
|
-
mode: AudioRoutingMode;
|
|
1480
|
-
activeRoutes: Array<{
|
|
1481
|
-
sourceId: string;
|
|
1482
|
-
targetIds: string[];
|
|
1483
|
-
mode: AudioRoutingMode;
|
|
1484
|
-
}>;
|
|
1485
|
-
registeredTargets: string[];
|
|
1486
|
-
zoneCount: number;
|
|
1487
|
-
};
|
|
1488
|
-
/**
|
|
1489
|
-
* Get or create a queue for a guild
|
|
1490
|
-
*/
|
|
1491
|
-
getQueue(guildId: string): MusicQueue;
|
|
1492
|
-
/**
|
|
1493
|
-
* Get all queues (for external monitoring)
|
|
1494
|
-
*/
|
|
1495
|
-
getQueues(): Map<string, MusicQueue>;
|
|
1496
|
-
/**
|
|
1497
|
-
* Get audio cache service
|
|
1498
|
-
*/
|
|
1499
|
-
getAudioCache(): AudioCacheService;
|
|
1500
|
-
/**
|
|
1501
|
-
* Add a track to the queue
|
|
1502
|
-
*/
|
|
1503
|
-
addTrack(guildId: string, track: Omit<QueuedTrack, "id" | "addedAt">): Promise<QueuedTrack>;
|
|
1504
|
-
/**
|
|
1505
|
-
* Skip current track
|
|
1506
|
-
*/
|
|
1507
|
-
skip(guildId: string, skipperEntityId?: UUID): Promise<boolean>;
|
|
1508
|
-
/**
|
|
1509
|
-
* Pause playback
|
|
1510
|
-
*/
|
|
1511
|
-
pause(guildId: string): Promise<void>;
|
|
1512
|
-
/**
|
|
1513
|
-
* Resume playback
|
|
1514
|
-
*/
|
|
1515
|
-
resume(guildId: string): Promise<void>;
|
|
1516
|
-
/**
|
|
1517
|
-
* Stop playback for a specific guild
|
|
1518
|
-
*/
|
|
1519
|
-
stopPlayback(guildId: string): Promise<void>;
|
|
1520
|
-
/**
|
|
1521
|
-
* Clear queue
|
|
1522
|
-
*/
|
|
1523
|
-
clear(guildId: string): void;
|
|
1524
|
-
/**
|
|
1525
|
-
* Get queue
|
|
1526
|
-
*/
|
|
1527
|
-
getQueueList(guildId: string): QueuedTrack[];
|
|
1528
|
-
/**
|
|
1529
|
-
* Get current track
|
|
1530
|
-
*/
|
|
1531
|
-
getCurrentTrack(guildId: string): QueuedTrack | null;
|
|
1532
|
-
/**
|
|
1533
|
-
* Get or create a broadcast for a guild
|
|
1534
|
-
* Broadcasts provide a clean interface for multiple consumers (Discord, Web, etc.)
|
|
1535
|
-
* to subscribe to the audio stream independently.
|
|
1536
|
-
* @param guildId Guild/server ID
|
|
1537
|
-
* @returns IAudioBroadcast instance
|
|
1538
|
-
*/
|
|
1539
|
-
getBroadcast(guildId: string): IAudioBroadcast;
|
|
1540
|
-
/**
|
|
1541
|
-
* Remove a track from queue
|
|
1542
|
-
*/
|
|
1543
|
-
removeTrack(guildId: string, trackId: string): boolean;
|
|
1544
|
-
/**
|
|
1545
|
-
* Shuffle queue
|
|
1546
|
-
*/
|
|
1547
|
-
shuffle(guildId: string): void;
|
|
1548
|
-
/**
|
|
1549
|
-
* Get whether queue is currently playing
|
|
1550
|
-
*/
|
|
1551
|
-
getIsPlaying(guildId: string): boolean;
|
|
1552
|
-
/**
|
|
1553
|
-
* Get whether playback is paused
|
|
1554
|
-
*/
|
|
1555
|
-
getIsPaused(guildId: string): boolean;
|
|
1556
|
-
/**
|
|
1557
|
-
* Handle track played event (optional analytics integration)
|
|
1558
|
-
*/
|
|
1559
|
-
private handleTrackPlayed;
|
|
1560
|
-
/**
|
|
1561
|
-
* Helper to get roomId from guildId via Discord service
|
|
1562
|
-
*/
|
|
1563
|
-
private getRoomIdFromGuild;
|
|
1564
|
-
/**
|
|
1565
|
-
* Update Discord listening activity to show currently playing track
|
|
1566
|
-
*/
|
|
1567
|
-
private updateDiscordListeningActivity;
|
|
1568
|
-
/**
|
|
1569
|
-
* Clear Discord activity when music stops
|
|
1570
|
-
*/
|
|
1571
|
-
private clearDiscordActivity;
|
|
1572
|
-
/**
|
|
1573
|
-
* Auto-subscribe Discord to broadcast (enables new architecture without requiring radio plugin)
|
|
1574
|
-
*
|
|
1575
|
-
* WHY AUTO-WIRING:
|
|
1576
|
-
* Design goal: music-player + discord should work together WITHOUT needing radio plugin.
|
|
1577
|
-
* Radio can add advanced features, but shouldn't be required for basic playback.
|
|
1578
|
-
*
|
|
1579
|
-
* THE CHALLENGE:
|
|
1580
|
-
* - MusicQueue pushes audio to broadcast
|
|
1581
|
-
* - Discord needs to subscribe to broadcast
|
|
1582
|
-
* - But music-player shouldn't import discord plugin (circular dependency)
|
|
1583
|
-
*
|
|
1584
|
-
* THE SOLUTION:
|
|
1585
|
-
* When a track starts, MusicService (which has access to runtime) checks:
|
|
1586
|
-
* 1. Is discord service available? (optional dependency)
|
|
1587
|
-
* 2. Is Discord connected to this guild?
|
|
1588
|
-
* 3. If yes, auto-subscribe Discord to the broadcast
|
|
1589
|
-
* 4. If not yet connected, attach listener to subscribe when it connects
|
|
1590
|
-
*
|
|
1591
|
-
* This happens transparently - user just plays music, and it works.
|
|
1592
|
-
*
|
|
1593
|
-
* WHY HANDLE RECONNECTS HERE:
|
|
1594
|
-
* Discord will emit status changes (connected/disconnected). MusicService listens
|
|
1595
|
-
* and auto-resubscribes on reconnect. This keeps the reconnection logic in one place.
|
|
1596
|
-
*
|
|
1597
|
-
* GRACEFUL DEGRADATION:
|
|
1598
|
-
* If discord service doesn't exist, or doesn't support IAudioSink, this silently
|
|
1599
|
-
* does nothing. Old architecture keeps working.
|
|
1600
|
-
*/
|
|
1601
|
-
private autoSubscribeDiscord;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
interface DetectedMusicEntity {
|
|
1605
|
-
type: "artist" | "album" | "song";
|
|
1606
|
-
name: string;
|
|
1607
|
-
confidence: number;
|
|
1608
|
-
context?: string;
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Service for detecting music entity names (artists, albums, songs) from text
|
|
1612
|
-
* Uses LLM for intelligent extraction with caching
|
|
1613
|
-
*/
|
|
1614
|
-
declare class MusicEntityDetectionHelper {
|
|
1615
|
-
capabilityDescription: string;
|
|
1616
|
-
private cache;
|
|
1617
|
-
private readonly CACHE_TTL;
|
|
1618
|
-
private readonly runtime?;
|
|
1619
|
-
constructor(runtime?: IAgentRuntime);
|
|
1620
|
-
stop(): Promise<void>;
|
|
1621
|
-
/**
|
|
1622
|
-
* Detect music entities from text using LLM
|
|
1623
|
-
*/
|
|
1624
|
-
detectEntities(text: string): Promise<DetectedMusicEntity[]>;
|
|
1625
|
-
/**
|
|
1626
|
-
* Clear cache
|
|
1627
|
-
*/
|
|
1628
|
-
clearCache(): void;
|
|
1629
|
-
/**
|
|
1630
|
-
* Clear expired cache entries
|
|
1631
|
-
*/
|
|
1632
|
-
clearExpiredCache(): void;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
/**
|
|
1636
|
-
* Audio features data structure for music tracks
|
|
1637
|
-
* Based on Spotify's audio features API
|
|
1638
|
-
*/
|
|
1639
|
-
interface AudioFeatures {
|
|
1640
|
-
trackId?: string;
|
|
1641
|
-
danceability: number;
|
|
1642
|
-
energy: number;
|
|
1643
|
-
valence: number;
|
|
1644
|
-
acousticness: number;
|
|
1645
|
-
instrumentalness: number;
|
|
1646
|
-
liveness: number;
|
|
1647
|
-
speechiness: number;
|
|
1648
|
-
key: number;
|
|
1649
|
-
mode: number;
|
|
1650
|
-
tempo: number;
|
|
1651
|
-
timeSignature: number;
|
|
1652
|
-
loudness: number;
|
|
1653
|
-
duration: number;
|
|
1654
|
-
source?: "spotify" | "computed" | "manual";
|
|
1655
|
-
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Simplified audio features for recommendations
|
|
1658
|
-
*/
|
|
1659
|
-
interface AudioFeatureSeed {
|
|
1660
|
-
targetDanceability?: number;
|
|
1661
|
-
targetEnergy?: number;
|
|
1662
|
-
targetValence?: number;
|
|
1663
|
-
targetTempo?: number;
|
|
1664
|
-
targetLoudness?: number;
|
|
1665
|
-
targetAcousticness?: number;
|
|
1666
|
-
targetInstrumentalness?: number;
|
|
1667
|
-
targetPopularity?: number;
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Track recommendation request
|
|
1671
|
-
*/
|
|
1672
|
-
interface RecommendationRequest {
|
|
1673
|
-
seedArtists?: string[];
|
|
1674
|
-
seedTracks?: string[];
|
|
1675
|
-
seedGenres?: string[];
|
|
1676
|
-
audioFeatures?: AudioFeatureSeed;
|
|
1677
|
-
limit?: number;
|
|
1678
|
-
}
|
|
1679
|
-
/**
|
|
1680
|
-
* Track recommendation result
|
|
1681
|
-
*/
|
|
1682
|
-
interface TrackRecommendation {
|
|
1683
|
-
trackName: string;
|
|
1684
|
-
artistName: string;
|
|
1685
|
-
albumName?: string;
|
|
1686
|
-
url?: string;
|
|
1687
|
-
previewUrl?: string;
|
|
1688
|
-
audioFeatures?: AudioFeatures;
|
|
1689
|
-
popularity?: number;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* Music information types
|
|
1694
|
-
*/
|
|
1695
|
-
interface TrackInfo {
|
|
1696
|
-
title: string;
|
|
1697
|
-
artist: string;
|
|
1698
|
-
album?: string;
|
|
1699
|
-
duration?: number;
|
|
1700
|
-
genre?: string[];
|
|
1701
|
-
year?: number;
|
|
1702
|
-
url?: string;
|
|
1703
|
-
thumbnail?: string;
|
|
1704
|
-
description?: string;
|
|
1705
|
-
tags?: string[];
|
|
1706
|
-
lyrics?: string;
|
|
1707
|
-
lyricsUrl?: string;
|
|
1708
|
-
}
|
|
1709
|
-
interface ArtistInfo {
|
|
1710
|
-
name: string;
|
|
1711
|
-
genres?: string[];
|
|
1712
|
-
bio?: string;
|
|
1713
|
-
image?: string;
|
|
1714
|
-
imageThumb?: string;
|
|
1715
|
-
imageLogo?: string;
|
|
1716
|
-
imageFanart?: string;
|
|
1717
|
-
imageBanner?: string;
|
|
1718
|
-
similarArtists?: string[];
|
|
1719
|
-
albums?: string[];
|
|
1720
|
-
topTracks?: string[];
|
|
1721
|
-
}
|
|
1722
|
-
interface AlbumInfo {
|
|
1723
|
-
title: string;
|
|
1724
|
-
artist: string;
|
|
1725
|
-
year?: number;
|
|
1726
|
-
genre?: string[];
|
|
1727
|
-
tracks?: string[];
|
|
1728
|
-
coverArt?: string;
|
|
1729
|
-
coverArtThumb?: string;
|
|
1730
|
-
coverArtCD?: string;
|
|
1731
|
-
description?: string;
|
|
1732
|
-
}
|
|
1733
|
-
interface MusicInfoResult {
|
|
1734
|
-
track?: TrackInfo;
|
|
1735
|
-
artist?: ArtistInfo;
|
|
1736
|
-
album?: AlbumInfo;
|
|
1737
|
-
source: "youtube" | "spotify" | "lastfm" | "musicbrainz" | "wikipedia" | "genius" | "theaudiodb";
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
/**
|
|
1741
|
-
* Service status tracking for music info services
|
|
1742
|
-
*/
|
|
1743
|
-
declare enum ServiceStatus {
|
|
1744
|
-
ACTIVE = "active",
|
|
1745
|
-
DEGRADED = "degraded",// Available but experiencing issues
|
|
1746
|
-
UNAVAILABLE = "unavailable",
|
|
1747
|
-
NOT_CONFIGURED = "not_configured"
|
|
1748
|
-
}
|
|
1749
|
-
interface ServiceHealth {
|
|
1750
|
-
status: ServiceStatus;
|
|
1751
|
-
lastChecked: number;
|
|
1752
|
-
lastError?: string;
|
|
1753
|
-
responseTime?: number;
|
|
1754
|
-
}
|
|
1755
|
-
interface MusicInfoServiceStatus {
|
|
1756
|
-
musicBrainz: ServiceHealth;
|
|
1757
|
-
lastFm: ServiceHealth;
|
|
1758
|
-
genius: ServiceHealth;
|
|
1759
|
-
theAudioDb: ServiceHealth;
|
|
1760
|
-
wikipedia: ServiceHealth;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
/**
|
|
1764
|
-
* Service for Wikipedia API access
|
|
1765
|
-
* Free, no authentication required
|
|
1766
|
-
* Rate limit: 2 requests per second (shared across all agents per instance)
|
|
1767
|
-
*/
|
|
1768
|
-
declare class WikipediaClient {
|
|
1769
|
-
capabilityDescription: string;
|
|
1770
|
-
private readonly baseUrl;
|
|
1771
|
-
private readonly searchUrl;
|
|
1772
|
-
private readonly rateLimiter;
|
|
1773
|
-
stop(): Promise<void>;
|
|
1774
|
-
/**
|
|
1775
|
-
* Search for a Wikipedia page and get summary
|
|
1776
|
-
*/
|
|
1777
|
-
private getPageSummary;
|
|
1778
|
-
/**
|
|
1779
|
-
* Search Wikipedia for pages matching a query
|
|
1780
|
-
*/
|
|
1781
|
-
private searchPages;
|
|
1782
|
-
/**
|
|
1783
|
-
* Get track information from Wikipedia
|
|
1784
|
-
*/
|
|
1785
|
-
getTrackInfo(trackName: string, artistName?: string): Promise<TrackInfo | null>;
|
|
1786
|
-
/**
|
|
1787
|
-
* Get artist information from Wikipedia
|
|
1788
|
-
*/
|
|
1789
|
-
getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
|
|
1790
|
-
/**
|
|
1791
|
-
* Get album information from Wikipedia
|
|
1792
|
-
*/
|
|
1793
|
-
getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
|
|
1794
|
-
/**
|
|
1795
|
-
* Clean Wikipedia extract text (remove HTML, truncate)
|
|
1796
|
-
*/
|
|
1797
|
-
private cleanExtract;
|
|
1798
|
-
/**
|
|
1799
|
-
* Try to extract artist name from Wikipedia extract
|
|
1800
|
-
*/
|
|
1801
|
-
private extractArtistFromExtract;
|
|
1802
|
-
/**
|
|
1803
|
-
* Extract related artists, influences, and similar acts from Wikipedia extract
|
|
1804
|
-
* Looks for patterns like "influenced by", "similar to", "associated acts", etc.
|
|
1805
|
-
* This data helps drive intelligent music selection and discovery
|
|
1806
|
-
*/
|
|
1807
|
-
private extractRelatedArtists;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
/**
|
|
1811
|
-
* Service for fetching music information from authoritative sources.
|
|
1812
|
-
* Track metadata comes from YouTube for direct URLs and MusicBrainz for
|
|
1813
|
-
* text queries; artist and album metadata comes from MusicBrainz only.
|
|
1814
|
-
*/
|
|
1815
|
-
declare class MusicInfoHelper {
|
|
1816
|
-
capabilityDescription: string;
|
|
1817
|
-
private cache;
|
|
1818
|
-
private readonly CACHE_TTL;
|
|
1819
|
-
private musicBrainzClient;
|
|
1820
|
-
private lastFmClient;
|
|
1821
|
-
private geniusClient;
|
|
1822
|
-
private theAudioDbClient;
|
|
1823
|
-
private readonly wikipediaClient;
|
|
1824
|
-
private serviceStatus;
|
|
1825
|
-
constructor(runtime?: IAgentRuntime, wikipediaClient?: WikipediaClient | null);
|
|
1826
|
-
stop(): Promise<void>;
|
|
1827
|
-
/**
|
|
1828
|
-
* Get service status for all integrated APIs
|
|
1829
|
-
*/
|
|
1830
|
-
getServiceStatus(): MusicInfoServiceStatus;
|
|
1831
|
-
/**
|
|
1832
|
-
* Validate API keys for all configured services
|
|
1833
|
-
* Updates service status based on validation results
|
|
1834
|
-
*/
|
|
1835
|
-
private validateApiKeys;
|
|
1836
|
-
/**
|
|
1837
|
-
* Extract track information from a YouTube URL or MusicBrainz lookup.
|
|
1838
|
-
*/
|
|
1839
|
-
getTrackInfo(urlOrTitle: string): Promise<MusicInfoResult | null>;
|
|
1840
|
-
/**
|
|
1841
|
-
* Parse title string to extract artist and track name
|
|
1842
|
-
*/
|
|
1843
|
-
private parseTitle;
|
|
1844
|
-
/**
|
|
1845
|
-
* Get artist information from MusicBrainz.
|
|
1846
|
-
*/
|
|
1847
|
-
getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
|
|
1848
|
-
/**
|
|
1849
|
-
* Get album information from MusicBrainz.
|
|
1850
|
-
*/
|
|
1851
|
-
getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
|
|
1852
|
-
/**
|
|
1853
|
-
* Check if a string is a YouTube URL
|
|
1854
|
-
*/
|
|
1855
|
-
private isYouTubeUrl;
|
|
1856
|
-
/**
|
|
1857
|
-
* Extract information from YouTube URL using play-dl
|
|
1858
|
-
*/
|
|
1859
|
-
private getInfoFromYouTube;
|
|
1860
|
-
/**
|
|
1861
|
-
* Clear cache
|
|
1862
|
-
*/
|
|
1863
|
-
clearCache(): void;
|
|
1864
|
-
/**
|
|
1865
|
-
* Clear expired cache entries
|
|
1866
|
-
*/
|
|
1867
|
-
clearExpiredCache(): void;
|
|
1868
|
-
/**
|
|
1869
|
-
* Pre-warm cache for a track (non-blocking)
|
|
1870
|
-
* This is called by plugin-dj to prepare caches before tracks are played
|
|
1871
|
-
* @param urlOrTitle - YouTube URL or track title
|
|
1872
|
-
*/
|
|
1873
|
-
prewarmTrackInfo(urlOrTitle: string): Promise<void>;
|
|
1874
|
-
/**
|
|
1875
|
-
* Pre-warm cache for multiple tracks (non-blocking)
|
|
1876
|
-
* @param tracks - Array of YouTube URLs or track titles
|
|
1877
|
-
*/
|
|
1878
|
-
prewarmTracks(tracks: string[]): Promise<void>;
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
/**
|
|
1882
|
-
* Client for Spotify Web API
|
|
1883
|
-
* Provides access to audio features and track recommendations
|
|
1884
|
-
*/
|
|
1885
|
-
declare class SpotifyClient {
|
|
1886
|
-
private clientId;
|
|
1887
|
-
private clientSecret;
|
|
1888
|
-
private token;
|
|
1889
|
-
private baseUrl;
|
|
1890
|
-
private authUrl;
|
|
1891
|
-
private lastRequestTime;
|
|
1892
|
-
private minRequestInterval;
|
|
1893
|
-
constructor(clientId?: string, clientSecret?: string);
|
|
1894
|
-
/**
|
|
1895
|
-
* Check if API credentials are configured
|
|
1896
|
-
*/
|
|
1897
|
-
isConfigured(): boolean;
|
|
1898
|
-
/**
|
|
1899
|
-
* Get or refresh access token
|
|
1900
|
-
*/
|
|
1901
|
-
private getAccessToken;
|
|
1902
|
-
/**
|
|
1903
|
-
* Rate limiting helper
|
|
1904
|
-
*/
|
|
1905
|
-
private rateLimit;
|
|
1906
|
-
/**
|
|
1907
|
-
* Search for a track on Spotify
|
|
1908
|
-
*/
|
|
1909
|
-
searchTrack(query: string): Promise<string | null>;
|
|
1910
|
-
/**
|
|
1911
|
-
* Get audio features for a track
|
|
1912
|
-
*/
|
|
1913
|
-
getAudioFeatures(trackId: string): Promise<AudioFeatures | null>;
|
|
1914
|
-
/**
|
|
1915
|
-
* Get audio features for a track by search query
|
|
1916
|
-
*/
|
|
1917
|
-
getAudioFeaturesByQuery(query: string): Promise<AudioFeatures | null>;
|
|
1918
|
-
/**
|
|
1919
|
-
* Get track recommendations
|
|
1920
|
-
*/
|
|
1921
|
-
getRecommendations(request: RecommendationRequest): Promise<TrackRecommendation[]>;
|
|
1922
|
-
/**
|
|
1923
|
-
* Validate API credentials
|
|
1924
|
-
*/
|
|
1925
|
-
validateCredentials(): Promise<boolean>;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
interface WikipediaExtractionContext {
|
|
1929
|
-
purpose: "dj_intro" | "music_selection" | "general_info" | "related_artists";
|
|
1930
|
-
currentArtist?: string;
|
|
1931
|
-
currentTrack?: string;
|
|
1932
|
-
currentAlbum?: string;
|
|
1933
|
-
}
|
|
1934
|
-
interface ExtractedMusicInfo {
|
|
1935
|
-
artist?: Partial<ArtistInfo>;
|
|
1936
|
-
track?: Partial<TrackInfo>;
|
|
1937
|
-
album?: Partial<AlbumInfo>;
|
|
1938
|
-
relatedArtists?: string[];
|
|
1939
|
-
influences?: string[];
|
|
1940
|
-
genres?: string[];
|
|
1941
|
-
interestingFacts?: string[];
|
|
1942
|
-
selectionSuggestions?: string[];
|
|
1943
|
-
}
|
|
1944
|
-
/**
|
|
1945
|
-
* Service that uses LLMs to dynamically extract relevant information from Wikipedia
|
|
1946
|
-
* Based on context (e.g., DJ intro, music selection), extracts different information
|
|
1947
|
-
*/
|
|
1948
|
-
declare class WikipediaExtractionHelper {
|
|
1949
|
-
capabilityDescription: string;
|
|
1950
|
-
private cache;
|
|
1951
|
-
private readonly CACHE_TTL;
|
|
1952
|
-
private readonly runtime?;
|
|
1953
|
-
private readonly wikipediaClient;
|
|
1954
|
-
constructor(runtime?: IAgentRuntime, wikipediaClient?: WikipediaClient | null);
|
|
1955
|
-
private getWikipediaService;
|
|
1956
|
-
stop(): Promise<void>;
|
|
1957
|
-
/**
|
|
1958
|
-
* Extract music information from Wikipedia using LLM based on context
|
|
1959
|
-
*/
|
|
1960
|
-
extractFromWikipedia(entityName: string, entityType: "artist" | "album" | "song", context: WikipediaExtractionContext): Promise<ExtractedMusicInfo | null>;
|
|
1961
|
-
/**
|
|
1962
|
-
* Build extraction prompt based on context
|
|
1963
|
-
*/
|
|
1964
|
-
private buildExtractionPrompt;
|
|
1965
|
-
/**
|
|
1966
|
-
* Parse LLM extraction response
|
|
1967
|
-
*/
|
|
1968
|
-
private parseExtractionResponse;
|
|
1969
|
-
/**
|
|
1970
|
-
* Extract list items from text using pattern
|
|
1971
|
-
*/
|
|
1972
|
-
private extractList;
|
|
1973
|
-
/**
|
|
1974
|
-
* Clear cache
|
|
1975
|
-
*/
|
|
1976
|
-
clearCache(): void;
|
|
1977
|
-
/**
|
|
1978
|
-
* Clear expired cache entries
|
|
1979
|
-
*/
|
|
1980
|
-
clearExpiredCache(): void;
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
interface YouTubeSearchResult {
|
|
1984
|
-
url: string;
|
|
1985
|
-
title: string;
|
|
1986
|
-
duration?: number;
|
|
1987
|
-
channel?: string;
|
|
1988
|
-
views?: number;
|
|
1989
|
-
}
|
|
1990
|
-
/**
|
|
1991
|
-
* Service for searching YouTube videos
|
|
1992
|
-
* Centralizes YouTube search logic for reuse across multiple actions
|
|
1993
|
-
*/
|
|
1994
|
-
declare class YouTubeSearchHelper {
|
|
1995
|
-
capabilityDescription: string;
|
|
1996
|
-
private cache;
|
|
1997
|
-
private readonly CACHE_TTL;
|
|
1998
|
-
stop(): Promise<void>;
|
|
1999
|
-
/**
|
|
2000
|
-
* Clear all cached searches
|
|
2001
|
-
*/
|
|
2002
|
-
clearCache(): void;
|
|
2003
|
-
/**
|
|
2004
|
-
* Search YouTube for videos
|
|
2005
|
-
* @param query - Search query string
|
|
2006
|
-
* @param options - Search options
|
|
2007
|
-
* @returns Array of search results
|
|
2008
|
-
*/
|
|
2009
|
-
search(query: string, options?: {
|
|
2010
|
-
limit?: number;
|
|
2011
|
-
includeShorts?: boolean;
|
|
2012
|
-
}): Promise<YouTubeSearchResult[]>;
|
|
2013
|
-
/**
|
|
2014
|
-
* Search and return the top result
|
|
2015
|
-
* @param query - Search query
|
|
2016
|
-
* @returns Top search result or null
|
|
2017
|
-
*/
|
|
2018
|
-
searchOne(query: string): Promise<YouTubeSearchResult | null>;
|
|
2019
|
-
/**
|
|
2020
|
-
* Validate if a URL is a valid YouTube video
|
|
2021
|
-
*/
|
|
2022
|
-
validateUrl(url: string): Promise<boolean>;
|
|
2023
|
-
/**
|
|
2024
|
-
* Get video info from URL
|
|
2025
|
-
*/
|
|
2026
|
-
getVideoInfo(url: string): Promise<YouTubeSearchResult | null>;
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
interface AggregatedRoomPreferences {
|
|
2030
|
-
favoriteTracks: Array<{
|
|
2031
|
-
url: string;
|
|
2032
|
-
title: string;
|
|
2033
|
-
requestedBy: UUID[];
|
|
2034
|
-
playCount: number;
|
|
2035
|
-
}>;
|
|
2036
|
-
dislikedTracks: string[];
|
|
2037
|
-
}
|
|
2038
|
-
declare class MusicLibraryService extends Service {
|
|
2039
|
-
static serviceType: string;
|
|
2040
|
-
capabilityDescription: string;
|
|
2041
|
-
readonly spotifyClient: SpotifyClient;
|
|
2042
|
-
readonly repetitionControl: RepetitionControl;
|
|
2043
|
-
private readonly youtubeSearch;
|
|
2044
|
-
private readonly wikipedia;
|
|
2045
|
-
private readonly musicInfo;
|
|
2046
|
-
private readonly entityDetection;
|
|
2047
|
-
private readonly wikipediaExtraction;
|
|
2048
|
-
constructor(runtime?: IAgentRuntime);
|
|
2049
|
-
static start(runtime: IAgentRuntime): Promise<MusicLibraryService>;
|
|
2050
|
-
stop(): Promise<void>;
|
|
2051
|
-
private ensureRuntime;
|
|
2052
|
-
addSong(songData: Parameters<typeof addSongToLibrary>[1]): Promise<LibrarySong>;
|
|
2053
|
-
getSong(url: string): Promise<LibrarySong | null>;
|
|
2054
|
-
getRecentSongs(limit?: number): Promise<LibrarySong[]>;
|
|
2055
|
-
getLastPlayedSong(): Promise<LibrarySong | null>;
|
|
2056
|
-
getMostPlayedSongs(limit?: number): Promise<LibrarySong[]>;
|
|
2057
|
-
searchLibrary(query: string, limit?: number): Promise<LibrarySong[]>;
|
|
2058
|
-
getLibraryStats(): Promise<{
|
|
2059
|
-
totalSongs: number;
|
|
2060
|
-
totalPlays: number;
|
|
2061
|
-
mostPlayed?: LibrarySong;
|
|
2062
|
-
}>;
|
|
2063
|
-
savePlaylist(entityId: UUID, playlist: Parameters<typeof savePlaylist>[2]): Promise<Playlist>;
|
|
2064
|
-
loadPlaylists(entityId: UUID): Promise<Playlist[]>;
|
|
2065
|
-
deletePlaylist(entityId: UUID, playlistId: string): Promise<boolean>;
|
|
2066
|
-
getUserPreferences(entityId: UUID): Promise<UserMusicPreferences | null>;
|
|
2067
|
-
getRoomPreferences(roomId: UUID): Promise<Map<UUID, UserMusicPreferences>>;
|
|
2068
|
-
getAggregatedRoomPreferences(roomId: UUID): Promise<AggregatedRoomPreferences>;
|
|
2069
|
-
trackTrackRequest(entityId: UUID, track: {
|
|
2070
|
-
url: string;
|
|
2071
|
-
title: string;
|
|
2072
|
-
}, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
2073
|
-
trackSkip(entityId: UUID, trackUrl: string, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
2074
|
-
trackFavorite(entityId: UUID, track: {
|
|
2075
|
-
url: string;
|
|
2076
|
-
title: string;
|
|
2077
|
-
}, roomId?: UUID, worldId?: UUID): Promise<void>;
|
|
2078
|
-
trackTrackPlayed(roomId: UUID, track: {
|
|
2079
|
-
url: string;
|
|
2080
|
-
title: string;
|
|
2081
|
-
}, duration: number, requestedBy?: {
|
|
2082
|
-
entityId: UUID;
|
|
2083
|
-
name: string;
|
|
2084
|
-
}): Promise<void>;
|
|
2085
|
-
getAnalytics(roomId: UUID): Promise<DJAnalytics | null>;
|
|
2086
|
-
getSongMemory(url: string): Promise<SongMemory | null>;
|
|
2087
|
-
recordSongPlay(song: Parameters<typeof recordSongPlay>[1], context: Parameters<typeof recordSongPlay>[2]): Promise<void>;
|
|
2088
|
-
recordSongRequest(song: Parameters<typeof recordSongRequest>[1], requester: Parameters<typeof recordSongRequest>[2]): Promise<void>;
|
|
2089
|
-
recordSongDedication(url: string, dedication: Parameters<typeof recordSongDedication>[2]): Promise<void>;
|
|
2090
|
-
trackDJTip(roomId: UUID, tip: Omit<DJTip, "roomId">): Promise<void>;
|
|
2091
|
-
getDJTipStats(): Promise<DJTipStats>;
|
|
2092
|
-
search(query: string, options?: {
|
|
2093
|
-
limit?: number;
|
|
2094
|
-
includeShorts?: boolean;
|
|
2095
|
-
}): Promise<YouTubeSearchResult[]>;
|
|
2096
|
-
searchYouTube(query: string, options?: {
|
|
2097
|
-
limit?: number;
|
|
2098
|
-
includeShorts?: boolean;
|
|
2099
|
-
}): Promise<YouTubeSearchResult[]>;
|
|
2100
|
-
searchOneYouTube(query: string): Promise<YouTubeSearchResult | null>;
|
|
2101
|
-
validateYouTubeUrl(url: string): Promise<boolean>;
|
|
2102
|
-
getYouTubeVideoInfo(url: string): Promise<YouTubeSearchResult | null>;
|
|
2103
|
-
getServiceStatus(): MusicInfoServiceStatus;
|
|
2104
|
-
getTrackInfo(urlOrTitle: string): Promise<MusicInfoResult | null>;
|
|
2105
|
-
getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
|
|
2106
|
-
getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
|
|
2107
|
-
prewarmTrackInfo(urlOrTitle: string): Promise<void>;
|
|
2108
|
-
prewarmTracks(tracks: string[]): Promise<void>;
|
|
2109
|
-
detectEntities(text: string): Promise<DetectedMusicEntity[]>;
|
|
2110
|
-
getWikipediaTrackInfo(trackName: string, artistName?: string): Promise<TrackInfo | null>;
|
|
2111
|
-
getWikipediaArtistInfo(artistName: string): Promise<ArtistInfo | null>;
|
|
2112
|
-
getWikipediaAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
|
|
2113
|
-
extractFromWikipedia(entityName: string, entityType: "artist" | "album" | "song", context: WikipediaExtractionContext): Promise<ExtractedMusicInfo | null>;
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
interface StoredTrack {
|
|
2117
|
-
url: string;
|
|
2118
|
-
title: string;
|
|
2119
|
-
artist?: string;
|
|
2120
|
-
album?: string;
|
|
2121
|
-
filePath: string;
|
|
2122
|
-
format: string;
|
|
2123
|
-
size: number;
|
|
2124
|
-
duration?: number;
|
|
2125
|
-
bitrate?: number;
|
|
2126
|
-
storedAt: number;
|
|
2127
|
-
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Music Storage Service
|
|
2130
|
-
* Stores high-quality original music files for archival and library purposes
|
|
2131
|
-
*
|
|
2132
|
-
* Unlike the Discord-optimized cache in music-player, this stores:
|
|
2133
|
-
* - Original quality files (no transcoding)
|
|
2134
|
-
* - Permanent storage (not temporary cache)
|
|
2135
|
-
* - Organized by artist/album/track
|
|
2136
|
-
* - Indexed for library browsing
|
|
2137
|
-
*/
|
|
2138
|
-
declare class MusicStorageService {
|
|
2139
|
-
private storageDir;
|
|
2140
|
-
private highQuality;
|
|
2141
|
-
private index;
|
|
2142
|
-
private readonly INDEX_FILE;
|
|
2143
|
-
constructor(storageDir?: string, highQuality?: boolean);
|
|
2144
|
-
/**
|
|
2145
|
-
* Check if a track is stored
|
|
2146
|
-
*/
|
|
2147
|
-
isStored(url: string): boolean;
|
|
2148
|
-
/**
|
|
2149
|
-
* Get stored track info
|
|
2150
|
-
*/
|
|
2151
|
-
getStoredTrack(url: string): StoredTrack | null;
|
|
2152
|
-
/**
|
|
2153
|
-
* Get all stored tracks
|
|
2154
|
-
*/
|
|
2155
|
-
getAllTracks(): StoredTrack[];
|
|
2156
|
-
/**
|
|
2157
|
-
* Store a track from YouTube URL
|
|
2158
|
-
*/
|
|
2159
|
-
storeTrack(url: string, metadata: {
|
|
2160
|
-
title: string;
|
|
2161
|
-
artist?: string;
|
|
2162
|
-
album?: string;
|
|
2163
|
-
}): Promise<StoredTrack>;
|
|
2164
|
-
/**
|
|
2165
|
-
* Get a readable stream for a stored track
|
|
2166
|
-
*/
|
|
2167
|
-
getStream(url: string): Readable | null;
|
|
2168
|
-
/**
|
|
2169
|
-
* Delete a stored track
|
|
2170
|
-
*/
|
|
2171
|
-
deleteTrack(url: string): boolean;
|
|
2172
|
-
/**
|
|
2173
|
-
* Get total storage size in bytes
|
|
2174
|
-
*/
|
|
2175
|
-
getTotalSize(): number;
|
|
2176
|
-
/**
|
|
2177
|
-
* Get storage statistics
|
|
2178
|
-
*/
|
|
2179
|
-
getStats(): {
|
|
2180
|
-
totalTracks: number;
|
|
2181
|
-
totalSize: number;
|
|
2182
|
-
totalDuration: number;
|
|
2183
|
-
byArtist: Map<string, number>;
|
|
2184
|
-
};
|
|
2185
|
-
/**
|
|
2186
|
-
* Load index from disk
|
|
2187
|
-
*/
|
|
2188
|
-
private loadIndex;
|
|
2189
|
-
/**
|
|
2190
|
-
* Save index to disk
|
|
2191
|
-
*/
|
|
2192
|
-
private saveIndex;
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
interface FetchProgress {
|
|
2196
|
-
stage: "checking_library" | "trying_ytdlp" | "searching_torrents" | "downloading_torrents" | "indexing" | "ready" | "failed";
|
|
2197
|
-
message: string;
|
|
2198
|
-
details?: unknown;
|
|
2199
|
-
}
|
|
2200
|
-
interface FetchResult {
|
|
2201
|
-
success: boolean;
|
|
2202
|
-
source: "library" | "ytdlp" | "torrent";
|
|
2203
|
-
url?: string;
|
|
2204
|
-
files?: string[];
|
|
2205
|
-
error?: string;
|
|
2206
|
-
}
|
|
2207
|
-
interface SmartFetchOptions {
|
|
2208
|
-
query: string;
|
|
2209
|
-
requestedBy?: UUID;
|
|
2210
|
-
onProgress?: (progress: FetchProgress) => void;
|
|
2211
|
-
preferredQuality?: "flac" | "mp3_320" | "any";
|
|
2212
|
-
parallelDownloads?: number;
|
|
2213
|
-
}
|
|
2214
|
-
/**
|
|
2215
|
-
* Smart music fetch service that tries multiple sources automatically
|
|
2216
|
-
* 1. Check music library
|
|
2217
|
-
* 2. Try yt-dlp (YouTube, SoundCloud, etc.)
|
|
2218
|
-
* 3. Search and download torrents (2-3 in parallel)
|
|
2219
|
-
* 4. Notify when ready
|
|
2220
|
-
*/
|
|
2221
|
-
declare class SmartMusicFetchService extends Service {
|
|
2222
|
-
static serviceType: string;
|
|
2223
|
-
capabilityDescription: string;
|
|
2224
|
-
static start(runtime: IAgentRuntime): Promise<SmartMusicFetchService>;
|
|
2225
|
-
stop(): Promise<void>;
|
|
2226
|
-
/**
|
|
2227
|
-
* Smart fetch music from any available source
|
|
2228
|
-
*/
|
|
2229
|
-
fetchMusic(options: SmartFetchOptions): Promise<FetchResult>;
|
|
2230
|
-
/**
|
|
2231
|
-
* Check if music exists in library
|
|
2232
|
-
*/
|
|
2233
|
-
private checkMusicLibrary;
|
|
2234
|
-
/**
|
|
2235
|
-
* Try to find and get URL from YouTube/SoundCloud via search
|
|
2236
|
-
*/
|
|
2237
|
-
private tryYtdlp;
|
|
2238
|
-
/**
|
|
2239
|
-
* Search for music torrents with quality scoring
|
|
2240
|
-
*/
|
|
2241
|
-
private searchMusicTorrents;
|
|
2242
|
-
/**
|
|
2243
|
-
* Calculate quality score for a torrent
|
|
2244
|
-
* Considers: quality match, seeders, and file format
|
|
2245
|
-
*/
|
|
2246
|
-
private calculateQualityScore;
|
|
2247
|
-
/**
|
|
2248
|
-
* Check if torrent is likely music
|
|
2249
|
-
*/
|
|
2250
|
-
private isMusicTorrent;
|
|
2251
|
-
/**
|
|
2252
|
-
* Download multiple torrents in parallel, return first to complete
|
|
2253
|
-
*/
|
|
2254
|
-
private downloadTorrentsParallel;
|
|
2255
|
-
/**
|
|
2256
|
-
* Wait for torrent to complete
|
|
2257
|
-
*/
|
|
2258
|
-
private waitForTorrentCompletion;
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
declare const musicPlugin: Plugin;
|
|
2262
|
-
|
|
2263
|
-
export { type AlbumInfo, type ArtistInfo, type AudioFeatureSeed, type AudioFeatures, type AudioRouteConfig, AudioRouter, type AudioRoutingMode, type AudioSubscription, Broadcast, type BroadcastState, type BroadcastTrackMetadata, type CrossFadeOptions, DEFAULT_DJ_INTRO_OPTIONS, DEFAULT_GUILD_SETTINGS, type DJAnalytics, type DJGuildSettings, type DJIntroOptions, type DJTip, type DJTipStats, type DetectedMusicEntity, type ExtractedMusicInfo, type FetchProgress, type FetchResult, type IAudioBroadcast, type LibrarySong, type MixConfig, type MixSession, MixSessionManager, MusicEntityDetectionHelper, MusicEntityDetectionHelper as MusicEntityDetectionService, MusicInfoHelper, type MusicInfoResult, MusicInfoHelper as MusicInfoService, type MusicInfoServiceStatus, MusicLibraryService, MusicService, MusicStorageService, type Playlist, type PlaylistTrack, type QueuedTrack, type RecommendationRequest, type ServiceHealth, ServiceStatus, type SmartFetchOptions, SmartMusicFetchService, type SongMemory, SpotifyClient, type StoredTrack, type TrackInfo, type TrackRecommendation, type UserMusicPreferences, WikipediaClient, type WikipediaExtractionContext, WikipediaExtractionHelper, WikipediaExtractionHelper as WikipediaExtractionService, WikipediaClient as WikipediaService, YouTubeSearchHelper, type YouTubeSearchResult, YouTubeSearchHelper as YouTubeSearchService, type Zone, ZoneManager, addSongToLibrary, buildIntroPrompt, musicPlugin as default, deletePlaylist, getAllConfiguredGuilds, getAnalytics, getDJGuildSettings, getDJIntroOptions, getDJTipStats, getLastPlayedSong, getLibraryStats, getMostPlayedSongs, getMostRequestedSongs, getRecentSongs, getRecentTips, getRoomPreferences, getSong, getSongMemory, getTopSongs, getTopTippers, getUserPreferences, loadPlaylists, musicAction, recordSongDedication, recordSongPlay, recordSongRequest, repetitionControl, resetDJGuildSettings, resetDJIntroOptions, savePlaylist, searchLibrary, setAutonomyLevel, setDJGuildSettings, setDJIntroOptions, toggleDJ, trackDJTip, trackFavorite, trackListenerSnapshot, trackSession, trackSkip, trackTrackPlayed, trackTrackRequest, tryHandleMusicPlayerStatusFallback, updateUserPreferences };
|