magmastream 2.9.0-dev.30 → 2.9.0-dev.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +187 -32
- package/dist/structures/Enums.js +5 -0
- package/dist/structures/Manager.js +64 -1
- package/dist/structures/Node.js +17 -4
- package/dist/structures/Player.js +132 -5
- package/dist/structures/Rest.js +3 -0
- package/dist/structures/Utils.js +21 -67
- package/package.json +1 -1
- package/dist/storage/CollectionPlayerStore.js +0 -77
- package/dist/storage/RedisPlayerStore.js +0 -156
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Collection } from '@discordjs/collection';
|
|
2
2
|
import { GatewayVoiceStateUpdate } from 'discord-api-types/v10';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
-
import WebSocket from 'ws';
|
|
4
|
+
import WebSocket, { WebSocket as WebSocket$1 } from 'ws';
|
|
5
5
|
import { User, ClientUser, Message, Client } from 'discord.js';
|
|
6
6
|
import { Redis } from 'ioredis';
|
|
7
7
|
import { Client as Client$1 } from 'eris';
|
|
@@ -28,6 +28,8 @@ declare class Rest {
|
|
|
28
28
|
private readonly url;
|
|
29
29
|
/** The Manager instance. */
|
|
30
30
|
manager: Manager;
|
|
31
|
+
/** Whether the node is a NodeLink. */
|
|
32
|
+
isNodeLink: boolean;
|
|
31
33
|
constructor(node: Node$1, manager: Manager);
|
|
32
34
|
/**
|
|
33
35
|
* Sets the session ID.
|
|
@@ -285,7 +287,12 @@ declare enum ManagerEventTypes {
|
|
|
285
287
|
TrackEnd = "trackEnd",
|
|
286
288
|
TrackError = "trackError",
|
|
287
289
|
TrackStart = "trackStart",
|
|
288
|
-
TrackStuck = "trackStuck"
|
|
290
|
+
TrackStuck = "trackStuck",
|
|
291
|
+
VoiceReceiverDisconnect = "voiceReceiverDisconnect",
|
|
292
|
+
VoiceReceiverConnect = "voiceReceiverConnect",
|
|
293
|
+
VoiceReceiverError = "voiceReceiverError",
|
|
294
|
+
VoiceReceiverStartSpeaking = "voiceReceiverStartSpeaking",
|
|
295
|
+
VoiceReceiverEndSpeaking = "voiceReceiverEndSpeaking"
|
|
289
296
|
}
|
|
290
297
|
/**
|
|
291
298
|
* Track End Reason Enum
|
|
@@ -801,6 +808,11 @@ interface ManagerEvents {
|
|
|
801
808
|
[ManagerEventTypes.TrackError]: [player: Player, track: Track, payload: TrackExceptionEvent];
|
|
802
809
|
[ManagerEventTypes.TrackStart]: [player: Player, track: Track, payload: TrackStartEvent];
|
|
803
810
|
[ManagerEventTypes.TrackStuck]: [player: Player, track: Track, payload: TrackStuckEvent];
|
|
811
|
+
[ManagerEventTypes.VoiceReceiverDisconnect]: [player: Player];
|
|
812
|
+
[ManagerEventTypes.VoiceReceiverConnect]: [player: Player];
|
|
813
|
+
[ManagerEventTypes.VoiceReceiverError]: [player: Player, error: Error];
|
|
814
|
+
[ManagerEventTypes.VoiceReceiverStartSpeaking]: [player: Player, data: unknown];
|
|
815
|
+
[ManagerEventTypes.VoiceReceiverEndSpeaking]: [player: Player, data: unknown];
|
|
804
816
|
}
|
|
805
817
|
/**
|
|
806
818
|
* Voice Packet
|
|
@@ -997,6 +1009,8 @@ interface NodeOptions {
|
|
|
997
1009
|
apiRequestTimeoutMs?: number;
|
|
998
1010
|
/** Priority of the node. */
|
|
999
1011
|
nodePriority?: number;
|
|
1012
|
+
/** Whether the node is a NodeLink. */
|
|
1013
|
+
isNodeLink?: boolean;
|
|
1000
1014
|
}
|
|
1001
1015
|
/**
|
|
1002
1016
|
* NodeOptions interface
|
|
@@ -1139,6 +1153,100 @@ interface Lyrics {
|
|
|
1139
1153
|
lines: LyricsLine[];
|
|
1140
1154
|
plugin: object[];
|
|
1141
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* NodeLink Get Lyrics Multiple interface
|
|
1158
|
+
*/
|
|
1159
|
+
interface NodeLinkGetLyricsMultiple {
|
|
1160
|
+
loadType: "lyricsMultiple";
|
|
1161
|
+
data: NodeLinkGetLyricsData[];
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* NodeLink Get Lyrics Empty interface
|
|
1165
|
+
*/
|
|
1166
|
+
interface NodeLinkGetLyricsEmpty {
|
|
1167
|
+
loadType: "empty";
|
|
1168
|
+
data: Record<never, never>;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* NodeLink Get Lyrics Data interface
|
|
1172
|
+
*/
|
|
1173
|
+
interface NodeLinkGetLyricsData {
|
|
1174
|
+
name: string;
|
|
1175
|
+
synced: boolean;
|
|
1176
|
+
data: {
|
|
1177
|
+
startTime?: number;
|
|
1178
|
+
endTime?: number;
|
|
1179
|
+
text: string;
|
|
1180
|
+
}[];
|
|
1181
|
+
rtl: boolean;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* NodeLink Get Lyrics Single interface
|
|
1185
|
+
*/
|
|
1186
|
+
interface NodeLinkGetLyricsSingle {
|
|
1187
|
+
loadType: "lyricsSingle";
|
|
1188
|
+
data: NodeLinkGetLyricsData;
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* NodeLink Get Lyrics Error interface
|
|
1192
|
+
*/
|
|
1193
|
+
interface NodeLinkGetLyricsError {
|
|
1194
|
+
loadType: "error";
|
|
1195
|
+
data: {
|
|
1196
|
+
message: string;
|
|
1197
|
+
severity: Severity;
|
|
1198
|
+
cause: string;
|
|
1199
|
+
trace?: string;
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
interface StartSpeakingEventVoiceReceiverData {
|
|
1203
|
+
/**
|
|
1204
|
+
* The user ID of the user who started speaking.
|
|
1205
|
+
*/
|
|
1206
|
+
userId: string;
|
|
1207
|
+
/**
|
|
1208
|
+
* The guild ID of the guild where the user started speaking.
|
|
1209
|
+
*/
|
|
1210
|
+
guildId: string;
|
|
1211
|
+
}
|
|
1212
|
+
interface EndSpeakingEventVoiceReceiverData {
|
|
1213
|
+
/**
|
|
1214
|
+
* The user ID of the user who stopped speaking.
|
|
1215
|
+
*/
|
|
1216
|
+
userId: string;
|
|
1217
|
+
/**
|
|
1218
|
+
* The guild ID of the guild where the user stopped speaking.
|
|
1219
|
+
*/
|
|
1220
|
+
guildId: string;
|
|
1221
|
+
/**
|
|
1222
|
+
* The audio data received from the user in base64.
|
|
1223
|
+
*/
|
|
1224
|
+
data: string;
|
|
1225
|
+
/**
|
|
1226
|
+
* The type of the audio data. Can be either opus or pcm. Older versions may include ogg/opus.
|
|
1227
|
+
*/
|
|
1228
|
+
type: "opus" | "pcm";
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Base Voice Receiver Event interface
|
|
1232
|
+
*/
|
|
1233
|
+
interface BaseVoiceReceiverEvent {
|
|
1234
|
+
op: "speak";
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Start Speaking Event Voice Receiver interface
|
|
1238
|
+
*/
|
|
1239
|
+
interface StartSpeakingEventVoiceReceiver extends BaseVoiceReceiverEvent {
|
|
1240
|
+
type: "startSpeakingEvent";
|
|
1241
|
+
data: StartSpeakingEventVoiceReceiverData;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* End Speaking Event Voice Receiver interface
|
|
1245
|
+
*/
|
|
1246
|
+
interface EndSpeakingEventVoiceReceiver extends BaseVoiceReceiverEvent {
|
|
1247
|
+
type: "endSpeakingEvent";
|
|
1248
|
+
data: EndSpeakingEventVoiceReceiverData;
|
|
1249
|
+
}
|
|
1142
1250
|
/**
|
|
1143
1251
|
* PlayerOptions interface
|
|
1144
1252
|
*/
|
|
@@ -1179,25 +1287,6 @@ interface EqualizerBand {
|
|
|
1179
1287
|
/** The gain amount being -0.25 to 1.00, 0.25 being double. */
|
|
1180
1288
|
gain: number;
|
|
1181
1289
|
}
|
|
1182
|
-
/**
|
|
1183
|
-
* PlayerStore interface
|
|
1184
|
-
*/
|
|
1185
|
-
interface PlayerStore {
|
|
1186
|
-
get(guildId: string): Promise<Player | undefined>;
|
|
1187
|
-
set(guildId: string, player: Player): Promise<void>;
|
|
1188
|
-
delete(guildId: string): Promise<void>;
|
|
1189
|
-
keys(): Promise<string[]>;
|
|
1190
|
-
has(guildId: string): Promise<boolean>;
|
|
1191
|
-
filter(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<Map<string, Player>>;
|
|
1192
|
-
find(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<Player | undefined>;
|
|
1193
|
-
map<T>(callback: (player: Player, guildId: string) => T | Promise<T>): Promise<T[]>;
|
|
1194
|
-
forEach(callback: (player: Player, guildId: string) => void | Promise<void>): Promise<void>;
|
|
1195
|
-
some(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<boolean>;
|
|
1196
|
-
every(predicate: (player: Player, guildId: string) => boolean | Promise<boolean>): Promise<boolean>;
|
|
1197
|
-
size(): Promise<number>;
|
|
1198
|
-
clear(): Promise<void>;
|
|
1199
|
-
entries(): AsyncIterableIterator<[string, Player]>;
|
|
1200
|
-
}
|
|
1201
1290
|
/**
|
|
1202
1291
|
* Queue interface
|
|
1203
1292
|
*/
|
|
@@ -1270,6 +1359,14 @@ type PlayerEvents = TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExc
|
|
|
1270
1359
|
* Load Type Enum type
|
|
1271
1360
|
*/
|
|
1272
1361
|
type LoadType = keyof typeof LoadTypes;
|
|
1362
|
+
/**
|
|
1363
|
+
* NodeLink Get Lyrics Enum type
|
|
1364
|
+
*/
|
|
1365
|
+
type NodeLinkGetLyrics = NodeLinkGetLyricsSingle | NodeLinkGetLyricsMultiple | NodeLinkGetLyricsEmpty | NodeLinkGetLyricsError;
|
|
1366
|
+
/**
|
|
1367
|
+
* Voice Receiver Event Enum type
|
|
1368
|
+
*/
|
|
1369
|
+
type VoiceReceiverEvent = StartSpeakingEventVoiceReceiver | EndSpeakingEventVoiceReceiver;
|
|
1273
1370
|
|
|
1274
1371
|
declare class Node$1 {
|
|
1275
1372
|
manager: Manager;
|
|
@@ -1285,11 +1382,14 @@ declare class Node$1 {
|
|
|
1285
1382
|
readonly rest: Rest;
|
|
1286
1383
|
/** Actual Lavalink information of the node. */
|
|
1287
1384
|
info: LavalinkInfo | null;
|
|
1385
|
+
/** Whether the node is a NodeLink. */
|
|
1386
|
+
isNodeLink: boolean;
|
|
1288
1387
|
private reconnectTimeout?;
|
|
1289
1388
|
private reconnectAttempts;
|
|
1290
1389
|
/**
|
|
1291
1390
|
* Creates an instance of Node.
|
|
1292
|
-
* @param
|
|
1391
|
+
* @param manager - The manager for the node.
|
|
1392
|
+
* @param options - The options for the node.
|
|
1293
1393
|
*/
|
|
1294
1394
|
constructor(manager: Manager, options: NodeOptions);
|
|
1295
1395
|
/** Returns if connected to the Node. */
|
|
@@ -1361,6 +1461,12 @@ declare class Node$1 {
|
|
|
1361
1461
|
* @emits {nodeDestroy} - Emits a nodeDestroy event if the maximum number of retry attempts is reached.
|
|
1362
1462
|
*/
|
|
1363
1463
|
private reconnect;
|
|
1464
|
+
/**
|
|
1465
|
+
* Upgrades the node to a NodeLink.
|
|
1466
|
+
*
|
|
1467
|
+
* @param request - The incoming message.
|
|
1468
|
+
*/
|
|
1469
|
+
private upgrade;
|
|
1364
1470
|
/**
|
|
1365
1471
|
* Handles the "open" event emitted by the WebSocket connection.
|
|
1366
1472
|
*
|
|
@@ -1492,9 +1598,9 @@ declare class Node$1 {
|
|
|
1492
1598
|
*
|
|
1493
1599
|
* @param {Track} track - The track to fetch the lyrics for.
|
|
1494
1600
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
1495
|
-
* @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
|
|
1601
|
+
* @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
|
|
1496
1602
|
*/
|
|
1497
|
-
getLyrics(track: Track, skipTrackSource?: boolean): Promise<Lyrics>;
|
|
1603
|
+
getLyrics(track: Track, skipTrackSource?: boolean): Promise<Lyrics | NodeLinkGetLyrics>;
|
|
1498
1604
|
/**
|
|
1499
1605
|
* Handles the event when a track becomes stuck during playback.
|
|
1500
1606
|
* Stops the current track and emits a `trackStuck` event.
|
|
@@ -1808,6 +1914,11 @@ declare class Manager extends EventEmitter {
|
|
|
1808
1914
|
* This is done to prevent stale state files from accumulating on the file system.
|
|
1809
1915
|
*/
|
|
1810
1916
|
private cleanupInactivePlayers;
|
|
1917
|
+
/**
|
|
1918
|
+
* Clears all player states from the file system.
|
|
1919
|
+
* This is done to prevent stale state files from accumulating on the file system.
|
|
1920
|
+
*/
|
|
1921
|
+
private clearAllPlayerStates;
|
|
1811
1922
|
/**
|
|
1812
1923
|
* Returns the nodes that has the least load.
|
|
1813
1924
|
* The load is calculated by dividing the lavalink load by the number of cores.
|
|
@@ -1884,6 +1995,12 @@ declare class Player {
|
|
|
1884
1995
|
private dynamicLoopInterval;
|
|
1885
1996
|
dynamicRepeatIntervalMs: number | null;
|
|
1886
1997
|
private static _manager;
|
|
1998
|
+
/** Should only be used when the node is a NodeLink */
|
|
1999
|
+
protected voiceReceiverWsClient: WebSocket$1 | null;
|
|
2000
|
+
protected isConnectToVoiceReceiver: boolean;
|
|
2001
|
+
protected voiceReceiverReconnectTimeout: NodeJS.Timeout | null;
|
|
2002
|
+
protected voiceReceiverAttempt: number;
|
|
2003
|
+
protected voiceReceiverReconnectTries: number;
|
|
1887
2004
|
/**
|
|
1888
2005
|
* Creates a new player, returns one if it already exists.
|
|
1889
2006
|
* @param options The player options.
|
|
@@ -2116,11 +2233,51 @@ declare class Player {
|
|
|
2116
2233
|
*/
|
|
2117
2234
|
getCurrentLyrics(skipTrackSource?: boolean): Promise<Lyrics>;
|
|
2118
2235
|
/**
|
|
2119
|
-
*
|
|
2120
|
-
* @
|
|
2121
|
-
* @
|
|
2236
|
+
* Sets up the voice receiver for the player.
|
|
2237
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is set up.
|
|
2238
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
2122
2239
|
*/
|
|
2123
|
-
|
|
2240
|
+
setupVoiceReceiver(): Promise<void>;
|
|
2241
|
+
/**
|
|
2242
|
+
* Removes the voice receiver for the player.
|
|
2243
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is removed.
|
|
2244
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
2245
|
+
*/
|
|
2246
|
+
removeVoiceReceiver(): Promise<void>;
|
|
2247
|
+
/**
|
|
2248
|
+
* Closes the voice receiver for the player.
|
|
2249
|
+
* @param {number} code - The code to close the voice receiver with.
|
|
2250
|
+
* @param {string} reason - The reason to close the voice receiver with.
|
|
2251
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is closed.
|
|
2252
|
+
*/
|
|
2253
|
+
private closeVoiceReceiver;
|
|
2254
|
+
/**
|
|
2255
|
+
* Reconnects the voice receiver for the player.
|
|
2256
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is reconnected.
|
|
2257
|
+
*/
|
|
2258
|
+
private reconnectVoiceReceiver;
|
|
2259
|
+
/**
|
|
2260
|
+
* Disconnects the voice receiver for the player.
|
|
2261
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is disconnected.
|
|
2262
|
+
*/
|
|
2263
|
+
private disconnectVoiceReceiver;
|
|
2264
|
+
/**
|
|
2265
|
+
* Opens the voice receiver for the player.
|
|
2266
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is opened.
|
|
2267
|
+
*/
|
|
2268
|
+
private openVoiceReceiver;
|
|
2269
|
+
/**
|
|
2270
|
+
* Handles a voice receiver message.
|
|
2271
|
+
* @param {string} payload - The payload to handle.
|
|
2272
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
|
|
2273
|
+
*/
|
|
2274
|
+
private onVoiceReceiverMessage;
|
|
2275
|
+
/**
|
|
2276
|
+
* Handles a voice receiver error.
|
|
2277
|
+
* @param {Error} error - The error to handle.
|
|
2278
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver error is handled.
|
|
2279
|
+
*/
|
|
2280
|
+
private onVoiceReceiverError;
|
|
2124
2281
|
}
|
|
2125
2282
|
|
|
2126
2283
|
declare class Filters {
|
|
@@ -2604,9 +2761,7 @@ declare abstract class AutoPlayUtils {
|
|
|
2604
2761
|
* @returns An array of recommended tracks.
|
|
2605
2762
|
*/
|
|
2606
2763
|
static getRecommendedTracksFromSource(track: Track, platform: string): Promise<Track[]>;
|
|
2607
|
-
static
|
|
2608
|
-
static transformSecret(buffer: Buffer): Buffer<ArrayBuffer>;
|
|
2609
|
-
static generateTotp(secretBuffer: Buffer): string;
|
|
2764
|
+
static generateTotp(): (string | number)[];
|
|
2610
2765
|
}
|
|
2611
2766
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
2612
2767
|
declare abstract class Structure {
|
|
@@ -2660,4 +2815,4 @@ declare class OceanicManager extends Manager {
|
|
|
2660
2815
|
}
|
|
2661
2816
|
|
|
2662
2817
|
export { AutoPlayPlatform, AutoPlayUtils, AvailableFilters, DetritusManager, DiscordJSManager, ErisManager, Filters, LoadTypes, Manager, ManagerEventTypes, Node$1 as Node, OceanicManager, Player, PlayerStateEventTypes, Plugin$1 as Plugin, Queue, Rest, SearchPlatform, SeverityTypes, SponsorBlockSegment, StateStorageType, StateTypes, Structure, TrackEndReasonTypes, TrackPartial, TrackSourceTypes, TrackUtils, UseNodeOptions };
|
|
2663
|
-
export type { CPUStats, DiscordPacket, EqualizerBand, Exception, Extendable, FrameStats, IQueue, LavaPlayer, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsLine, ManagerEvents, ManagerInitOptions, ManagerOptions, MemoryStats, NodeMessage, NodeOptions, NodeStats, Payload, PlayOptions, PlayerEvent, PlayerEventType, PlayerEvents, PlayerOptions, PlayerStateUpdateEvent,
|
|
2818
|
+
export type { CPUStats, DiscordPacket, EndSpeakingEventVoiceReceiver, EndSpeakingEventVoiceReceiverData, EqualizerBand, Exception, Extendable, FrameStats, IQueue, LavaPlayer, LavalinkInfo, LavalinkResponse, LoadType, Lyrics, LyricsLine, ManagerEvents, ManagerInitOptions, ManagerOptions, MemoryStats, NodeLinkGetLyrics, NodeLinkGetLyricsEmpty, NodeLinkGetLyricsError, NodeLinkGetLyricsMultiple, NodeLinkGetLyricsSingle, NodeMessage, NodeOptions, NodeStats, Payload, PlayOptions, PlayerEvent, PlayerEventType, PlayerEvents, PlayerOptions, PlayerStateUpdateEvent, PlayerUpdate, PlayerUpdateVoiceState, PlaylistData, PlaylistInfoData, PlaylistRawData, RedisConfig, SearchQuery, SearchResult, Severity, Sizes, SponsorBlockChapterStarted, SponsorBlockChaptersLoaded, SponsorBlockSegmentEventType, SponsorBlockSegmentEvents, SponsorBlockSegmentSkipped, SponsorBlockSegmentsLoaded, StartSpeakingEventVoiceReceiver, StartSpeakingEventVoiceReceiverData, StateStorageOptions, Track, TrackData, TrackDataInfo, TrackEndEvent, TrackEndReason, TrackExceptionEvent, TrackPluginInfo, TrackSourceName, TrackStartEvent, TrackStuckEvent, UseNodeOption, VoicePacket, VoiceReceiverEvent, VoiceServer, VoiceServerUpdate, VoiceState, WebSocketClosedEvent };
|
package/dist/structures/Enums.js
CHANGED
|
@@ -163,6 +163,11 @@ var ManagerEventTypes;
|
|
|
163
163
|
ManagerEventTypes["TrackError"] = "trackError";
|
|
164
164
|
ManagerEventTypes["TrackStart"] = "trackStart";
|
|
165
165
|
ManagerEventTypes["TrackStuck"] = "trackStuck";
|
|
166
|
+
ManagerEventTypes["VoiceReceiverDisconnect"] = "voiceReceiverDisconnect";
|
|
167
|
+
ManagerEventTypes["VoiceReceiverConnect"] = "voiceReceiverConnect";
|
|
168
|
+
ManagerEventTypes["VoiceReceiverError"] = "voiceReceiverError";
|
|
169
|
+
ManagerEventTypes["VoiceReceiverStartSpeaking"] = "voiceReceiverStartSpeaking";
|
|
170
|
+
ManagerEventTypes["VoiceReceiverEndSpeaking"] = "voiceReceiverEndSpeaking";
|
|
166
171
|
})(ManagerEventTypes || (exports.ManagerEventTypes = ManagerEventTypes = {}));
|
|
167
172
|
/**
|
|
168
173
|
* Track End Reason Enum
|
|
@@ -791,6 +791,9 @@ class Manager extends events_1.EventEmitter {
|
|
|
791
791
|
async handleShutdown() {
|
|
792
792
|
console.warn("\x1b[31m%s\x1b[0m", "MAGMASTREAM WARNING: Shutting down! Please wait, saving active players...");
|
|
793
793
|
try {
|
|
794
|
+
if (this.options.stateStorage.type === Enums_1.StateStorageType.Collection) {
|
|
795
|
+
await this.clearAllPlayerStates();
|
|
796
|
+
}
|
|
794
797
|
const savePromises = Array.from(this.players.keys()).map(async (guildId) => {
|
|
795
798
|
try {
|
|
796
799
|
await this.savePlayerState(guildId);
|
|
@@ -925,6 +928,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
925
928
|
guildId: player.guildId,
|
|
926
929
|
data: { voice: { token, endpoint, sessionId } },
|
|
927
930
|
});
|
|
931
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} and endpoint ${endpoint} and sessionId ${sessionId}`);
|
|
928
932
|
return;
|
|
929
933
|
}
|
|
930
934
|
/**
|
|
@@ -935,6 +939,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
935
939
|
* @emits {playerDisconnect} - Emits a player disconnect event if the channel ID is null.
|
|
936
940
|
*/
|
|
937
941
|
async handleVoiceStateUpdate(player, update) {
|
|
942
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice state for player ${player.guildId} with channel id ${update.channel_id} and session id ${update.session_id}`);
|
|
938
943
|
if (update.channel_id) {
|
|
939
944
|
if (player.voiceChannelId !== update.channel_id) {
|
|
940
945
|
this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
|
|
@@ -946,7 +951,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
946
951
|
this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
|
|
947
952
|
player.voiceChannelId = null;
|
|
948
953
|
player.voiceState = Object.assign({});
|
|
949
|
-
await player.
|
|
954
|
+
await player.pause(true);
|
|
950
955
|
return;
|
|
951
956
|
}
|
|
952
957
|
/**
|
|
@@ -1052,6 +1057,64 @@ class Manager extends events_1.EventEmitter {
|
|
|
1052
1057
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive players: ${error}`);
|
|
1053
1058
|
}
|
|
1054
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Clears all player states from the file system.
|
|
1062
|
+
* This is done to prevent stale state files from accumulating on the file system.
|
|
1063
|
+
*/
|
|
1064
|
+
async clearAllPlayerStates() {
|
|
1065
|
+
switch (this.options.stateStorage.type) {
|
|
1066
|
+
case Enums_1.StateStorageType.Collection: {
|
|
1067
|
+
const configDir = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "players");
|
|
1068
|
+
try {
|
|
1069
|
+
// Check if the directory exists, and create it if it doesn't
|
|
1070
|
+
await promises_1.default.access(configDir).catch(async () => {
|
|
1071
|
+
await promises_1.default.mkdir(configDir, { recursive: true });
|
|
1072
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Created directory: ${configDir}`);
|
|
1073
|
+
});
|
|
1074
|
+
const files = await promises_1.default.readdir(configDir);
|
|
1075
|
+
await Promise.all(files.map((file) => promises_1.default.unlink(path_1.default.join(configDir, file)).catch((err) => console.warn(`Failed to delete file ${file}:`, err))));
|
|
1076
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared all player state files in ${configDir}`);
|
|
1077
|
+
}
|
|
1078
|
+
catch (err) {
|
|
1079
|
+
console.error("Error clearing player state files:", err);
|
|
1080
|
+
}
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case Enums_1.StateStorageType.Redis: {
|
|
1084
|
+
const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
|
|
1085
|
+
? this.options.stateStorage.redisConfig.prefix
|
|
1086
|
+
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
1087
|
+
const pattern = `${prefix}playerstore:*`;
|
|
1088
|
+
try {
|
|
1089
|
+
const stream = this.redis.scanStream({
|
|
1090
|
+
match: pattern,
|
|
1091
|
+
count: 100,
|
|
1092
|
+
});
|
|
1093
|
+
let totalDeleted = 0;
|
|
1094
|
+
stream.on("data", async (keys) => {
|
|
1095
|
+
if (keys.length) {
|
|
1096
|
+
const pipeline = this.redis.pipeline();
|
|
1097
|
+
keys.forEach((key) => pipeline.unlink(key));
|
|
1098
|
+
await pipeline.exec();
|
|
1099
|
+
totalDeleted += keys.length;
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
stream.on("end", () => {
|
|
1103
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleared ${totalDeleted} Redis player state keys (pattern: ${pattern})`);
|
|
1104
|
+
});
|
|
1105
|
+
stream.on("error", (err) => {
|
|
1106
|
+
console.error("Error during Redis SCAN stream:", err);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
catch (err) {
|
|
1110
|
+
console.error("Failed to clear Redis player state keys:", err);
|
|
1111
|
+
}
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
default:
|
|
1115
|
+
console.warn("[MANAGER] No valid stateStorage.type set, skipping state clearing.");
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1055
1118
|
/**
|
|
1056
1119
|
* Returns the nodes that has the least load.
|
|
1057
1120
|
* The load is calculated by dividing the lavalink load by the number of cores.
|
package/dist/structures/Node.js
CHANGED
|
@@ -31,11 +31,14 @@ class Node {
|
|
|
31
31
|
rest;
|
|
32
32
|
/** Actual Lavalink information of the node. */
|
|
33
33
|
info = null;
|
|
34
|
+
/** Whether the node is a NodeLink. */
|
|
35
|
+
isNodeLink = false;
|
|
34
36
|
reconnectTimeout;
|
|
35
37
|
reconnectAttempts = 1;
|
|
36
38
|
/**
|
|
37
39
|
* Creates an instance of Node.
|
|
38
|
-
* @param
|
|
40
|
+
* @param manager - The manager for the node.
|
|
41
|
+
* @param options - The options for the node.
|
|
39
42
|
*/
|
|
40
43
|
constructor(manager, options) {
|
|
41
44
|
this.manager = manager;
|
|
@@ -185,6 +188,7 @@ class Node {
|
|
|
185
188
|
this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
186
189
|
this.socket.on("open", this.open.bind(this));
|
|
187
190
|
this.socket.on("close", this.close.bind(this));
|
|
191
|
+
this.socket.on("upgrade", (request) => this.upgrade(request));
|
|
188
192
|
this.socket.on("message", this.message.bind(this));
|
|
189
193
|
this.socket.on("error", this.error.bind(this));
|
|
190
194
|
const debugInfo = {
|
|
@@ -287,6 +291,14 @@ class Node {
|
|
|
287
291
|
this.reconnectAttempts++;
|
|
288
292
|
}, this.options.retryDelayMs);
|
|
289
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Upgrades the node to a NodeLink.
|
|
296
|
+
*
|
|
297
|
+
* @param request - The incoming message.
|
|
298
|
+
*/
|
|
299
|
+
upgrade(request) {
|
|
300
|
+
this.isNodeLink = this.options.isNodeLink ?? Boolean(request.headers.isnodelink) ?? false;
|
|
301
|
+
}
|
|
290
302
|
/**
|
|
291
303
|
* Handles the "open" event emitted by the WebSocket connection.
|
|
292
304
|
*
|
|
@@ -727,13 +739,14 @@ class Node {
|
|
|
727
739
|
*
|
|
728
740
|
* @param {Track} track - The track to fetch the lyrics for.
|
|
729
741
|
* @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
|
|
730
|
-
* @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
|
|
742
|
+
* @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
|
|
731
743
|
*/
|
|
732
744
|
async getLyrics(track, skipTrackSource = false) {
|
|
745
|
+
if (this.isNodeLink) {
|
|
746
|
+
return (await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`));
|
|
747
|
+
}
|
|
733
748
|
if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin"))
|
|
734
749
|
throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node: ${this.options.identifier}`);
|
|
735
|
-
// Make a GET request to the Lavalink node to fetch the lyrics
|
|
736
|
-
// The request includes the track URL and the skipTrackSource parameter
|
|
737
750
|
return ((await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`)) || {
|
|
738
751
|
source: null,
|
|
739
752
|
provider: null,
|
|
@@ -10,6 +10,7 @@ const playerCheck_1 = tslib_1.__importDefault(require("../utils/playerCheck"));
|
|
|
10
10
|
const RedisQueue_1 = require("./RedisQueue");
|
|
11
11
|
// import { IQueue, Lyrics, PlayerOptions, PlayerUpdateVoiceState, PlayOptions, SearchQuery, SearchResult, Track, VoiceState } from "./Types";
|
|
12
12
|
const Enums_1 = require("./Enums");
|
|
13
|
+
const ws_1 = require("ws");
|
|
13
14
|
class Player {
|
|
14
15
|
options;
|
|
15
16
|
/** The Queue for the Player. */
|
|
@@ -58,6 +59,12 @@ class Player {
|
|
|
58
59
|
dynamicLoopInterval = null;
|
|
59
60
|
dynamicRepeatIntervalMs = null;
|
|
60
61
|
static _manager;
|
|
62
|
+
/** Should only be used when the node is a NodeLink */
|
|
63
|
+
voiceReceiverWsClient;
|
|
64
|
+
isConnectToVoiceReceiver;
|
|
65
|
+
voiceReceiverReconnectTimeout;
|
|
66
|
+
voiceReceiverAttempt;
|
|
67
|
+
voiceReceiverReconnectTries;
|
|
61
68
|
/**
|
|
62
69
|
* Creates a new player, returns one if it already exists.
|
|
63
70
|
* @param options The player options.
|
|
@@ -911,12 +918,132 @@ class Player {
|
|
|
911
918
|
return result;
|
|
912
919
|
}
|
|
913
920
|
/**
|
|
914
|
-
*
|
|
915
|
-
* @
|
|
916
|
-
* @
|
|
921
|
+
* Sets up the voice receiver for the player.
|
|
922
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is set up.
|
|
923
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
917
924
|
*/
|
|
918
|
-
|
|
919
|
-
|
|
925
|
+
async setupVoiceReceiver() {
|
|
926
|
+
if (!this.node.isNodeLink)
|
|
927
|
+
throw new Error("This function is only available for NodeLinks");
|
|
928
|
+
if (this.voiceReceiverWsClient)
|
|
929
|
+
await this.removeVoiceReceiver();
|
|
930
|
+
const headers = {
|
|
931
|
+
Authorization: this.node.options.password,
|
|
932
|
+
"User-Id": this.manager.options.clientId,
|
|
933
|
+
"Guild-Id": this.guildId,
|
|
934
|
+
"Client-Name": this.manager.options.clientName,
|
|
935
|
+
};
|
|
936
|
+
const { host, useSSL, port } = this.node.options;
|
|
937
|
+
this.voiceReceiverWsClient = new ws_1.WebSocket(`${useSSL ? "wss" : "ws"}://${host}:${port}/connection/data`, { headers });
|
|
938
|
+
this.voiceReceiverWsClient.on("open", () => this.openVoiceReceiver());
|
|
939
|
+
this.voiceReceiverWsClient.on("error", (err) => this.onVoiceReceiverError(err));
|
|
940
|
+
this.voiceReceiverWsClient.on("message", (data) => this.onVoiceReceiverMessage(data.toString()));
|
|
941
|
+
this.voiceReceiverWsClient.on("close", (code, reason) => this.closeVoiceReceiver(code, reason.toString()));
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Removes the voice receiver for the player.
|
|
945
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is removed.
|
|
946
|
+
* @throws {Error} - If the node is not a NodeLink.
|
|
947
|
+
*/
|
|
948
|
+
async removeVoiceReceiver() {
|
|
949
|
+
if (!this.node.isNodeLink)
|
|
950
|
+
throw new Error("This function is only available for NodeLinks");
|
|
951
|
+
if (this.voiceReceiverWsClient) {
|
|
952
|
+
this.voiceReceiverWsClient.close(1000, "destroy");
|
|
953
|
+
this.voiceReceiverWsClient.removeAllListeners();
|
|
954
|
+
this.voiceReceiverWsClient = null;
|
|
955
|
+
}
|
|
956
|
+
this.isConnectToVoiceReceiver = false;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Closes the voice receiver for the player.
|
|
960
|
+
* @param {number} code - The code to close the voice receiver with.
|
|
961
|
+
* @param {string} reason - The reason to close the voice receiver with.
|
|
962
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is closed.
|
|
963
|
+
*/
|
|
964
|
+
async closeVoiceReceiver(code, reason) {
|
|
965
|
+
await this.disconnectVoiceReceiver();
|
|
966
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Closed voice receiver for player ${this.guildId} with code ${code} and reason ${reason}`);
|
|
967
|
+
if (code !== 1000)
|
|
968
|
+
await this.reconnectVoiceReceiver();
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Reconnects the voice receiver for the player.
|
|
972
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is reconnected.
|
|
973
|
+
*/
|
|
974
|
+
async reconnectVoiceReceiver() {
|
|
975
|
+
this.voiceReceiverReconnectTimeout = setTimeout(async () => {
|
|
976
|
+
if (this.voiceReceiverAttempt > this.voiceReceiverReconnectTries)
|
|
977
|
+
throw new Error("Failed to reconnect to voice receiver");
|
|
978
|
+
this.voiceReceiverWsClient?.removeAllListeners();
|
|
979
|
+
this.voiceReceiverWsClient = null;
|
|
980
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Reconnecting to voice receiver for player ${this.guildId}`);
|
|
981
|
+
await this.setupVoiceReceiver();
|
|
982
|
+
this.voiceReceiverAttempt++;
|
|
983
|
+
}, this.node.options.retryDelayMs);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Disconnects the voice receiver for the player.
|
|
987
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is disconnected.
|
|
988
|
+
*/
|
|
989
|
+
async disconnectVoiceReceiver() {
|
|
990
|
+
if (!this.isConnectToVoiceReceiver)
|
|
991
|
+
return;
|
|
992
|
+
this.voiceReceiverWsClient?.close(1000, "destroy");
|
|
993
|
+
this.voiceReceiverWsClient?.removeAllListeners();
|
|
994
|
+
this.voiceReceiverWsClient = null;
|
|
995
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Disconnected from voice receiver for player ${this.guildId}`);
|
|
996
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverDisconnect, this);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Opens the voice receiver for the player.
|
|
1000
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver is opened.
|
|
1001
|
+
*/
|
|
1002
|
+
async openVoiceReceiver() {
|
|
1003
|
+
if (this.voiceReceiverReconnectTimeout)
|
|
1004
|
+
clearTimeout(this.voiceReceiverReconnectTimeout);
|
|
1005
|
+
this.voiceReceiverReconnectTimeout = null;
|
|
1006
|
+
this.isConnectToVoiceReceiver = true;
|
|
1007
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Opened voice receiver for player ${this.guildId}`);
|
|
1008
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverConnect, this);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Handles a voice receiver message.
|
|
1012
|
+
* @param {string} payload - The payload to handle.
|
|
1013
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver message is handled.
|
|
1014
|
+
*/
|
|
1015
|
+
async onVoiceReceiverMessage(payload) {
|
|
1016
|
+
const packet = JSON.parse(payload);
|
|
1017
|
+
if (!packet?.op)
|
|
1018
|
+
return;
|
|
1019
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved a payload: ${JSON.stringify(payload)}`);
|
|
1020
|
+
switch (packet.type) {
|
|
1021
|
+
case "startSpeakingEvent": {
|
|
1022
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverStartSpeaking, this, packet.data);
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
case "endSpeakingEvent": {
|
|
1026
|
+
const data = {
|
|
1027
|
+
...packet.data,
|
|
1028
|
+
data: Buffer.from(packet.data.data, "base64"),
|
|
1029
|
+
};
|
|
1030
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverEndSpeaking, this, data);
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
default: {
|
|
1034
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver recieved an unknown payload: ${JSON.stringify(payload)}`);
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Handles a voice receiver error.
|
|
1041
|
+
* @param {Error} error - The error to handle.
|
|
1042
|
+
* @returns {Promise<void>} - A promise that resolves when the voice receiver error is handled.
|
|
1043
|
+
*/
|
|
1044
|
+
async onVoiceReceiverError(error) {
|
|
1045
|
+
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver error for player ${this.guildId}: ${error.message}`);
|
|
1046
|
+
this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverError, this, error);
|
|
920
1047
|
}
|
|
921
1048
|
}
|
|
922
1049
|
exports.Player = Player;
|
package/dist/structures/Rest.js
CHANGED
|
@@ -16,12 +16,15 @@ class Rest {
|
|
|
16
16
|
url;
|
|
17
17
|
/** The Manager instance. */
|
|
18
18
|
manager;
|
|
19
|
+
/** Whether the node is a NodeLink. */
|
|
20
|
+
isNodeLink = false;
|
|
19
21
|
constructor(node, manager) {
|
|
20
22
|
this.node = node;
|
|
21
23
|
this.url = `http${node.options.useSSL ? "s" : ""}://${node.options.host}:${node.options.port}`;
|
|
22
24
|
this.sessionId = node.sessionId;
|
|
23
25
|
this.password = node.options.password;
|
|
24
26
|
this.manager = manager;
|
|
27
|
+
this.isNodeLink = node.isNodeLink;
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Sets the session ID.
|
package/dist/structures/Utils.js
CHANGED
|
@@ -5,7 +5,6 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const axios_1 = tslib_1.__importDefault(require("axios"));
|
|
6
6
|
const jsdom_1 = require("jsdom");
|
|
7
7
|
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
8
|
-
const cheerio_1 = tslib_1.__importDefault(require("cheerio"));
|
|
9
8
|
const Enums_1 = require("./Enums");
|
|
10
9
|
/** @hidden */
|
|
11
10
|
const SIZES = ["0", "1", "2", "3", "default", "mqdefault", "hqdefault", "maxresdefault"];
|
|
@@ -278,31 +277,17 @@ class AutoPlayUtils {
|
|
|
278
277
|
}
|
|
279
278
|
track = res.tracks[0];
|
|
280
279
|
}
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
reason: "transport",
|
|
290
|
-
productType: "embed",
|
|
291
|
-
totp,
|
|
292
|
-
totpVer: 5,
|
|
293
|
-
ts: timestamp,
|
|
294
|
-
};
|
|
295
|
-
return params;
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
console.error("Failed to generate Spotify TOTP params:", error);
|
|
299
|
-
throw error;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
280
|
+
const [totp, timestamp] = this.generateTotp();
|
|
281
|
+
const params = {
|
|
282
|
+
reason: "init",
|
|
283
|
+
productType: "web-player",
|
|
284
|
+
totp: totp,
|
|
285
|
+
totpVer: 5,
|
|
286
|
+
ts: timestamp,
|
|
287
|
+
};
|
|
302
288
|
let body;
|
|
303
289
|
try {
|
|
304
|
-
const
|
|
305
|
-
const response = await axios_1.default.get("https://open.spotify.com/get_access_token", {
|
|
290
|
+
const response = await axios_1.default.get("https://open.spotify.com/api/token", {
|
|
306
291
|
params,
|
|
307
292
|
headers: {
|
|
308
293
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.178 Spotify/1.2.65.255 Safari/537.36",
|
|
@@ -310,13 +295,13 @@ class AutoPlayUtils {
|
|
|
310
295
|
Referer: "https://open.spotify.com/",
|
|
311
296
|
Origin: "https://open.spotify.com",
|
|
312
297
|
"Accept-Language": "en",
|
|
313
|
-
"Content-Type": "application/json",
|
|
314
298
|
},
|
|
315
299
|
});
|
|
316
300
|
body = response.data;
|
|
317
301
|
}
|
|
318
302
|
catch (error) {
|
|
319
|
-
|
|
303
|
+
const status = error.response?.status ?? "No response";
|
|
304
|
+
console.error("[AutoPlay] Failed to get spotify access token:", status);
|
|
320
305
|
return [];
|
|
321
306
|
}
|
|
322
307
|
let json;
|
|
@@ -673,51 +658,20 @@ class AutoPlayUtils {
|
|
|
673
658
|
return [];
|
|
674
659
|
}
|
|
675
660
|
}
|
|
676
|
-
static
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
.map((_, el) => $(el).attr("src"))
|
|
683
|
-
.get()
|
|
684
|
-
.find((src) => src.includes("mobile-web-player") && !src.includes("vendor"));
|
|
685
|
-
if (!scriptUrl)
|
|
686
|
-
throw new Error("Secret script not found");
|
|
687
|
-
// Full URL fix (if needed)
|
|
688
|
-
const fullScriptUrl = scriptUrl.startsWith("http") ? scriptUrl : "https://open.spotify.com" + scriptUrl;
|
|
689
|
-
// Step 3: Fetch the script content
|
|
690
|
-
const scriptRes = await axios_1.default.get(fullScriptUrl);
|
|
691
|
-
const scriptContent = scriptRes.data;
|
|
692
|
-
// Step 4: Extract secret array using regex
|
|
693
|
-
const secretPattern = /\(\[(\d+(?:,\d+)+)]\)/;
|
|
694
|
-
const match = secretPattern.exec(scriptContent);
|
|
695
|
-
if (!match)
|
|
696
|
-
throw new Error("Secret array not found in script");
|
|
697
|
-
// Parse the secret array into bytes
|
|
698
|
-
const secretArray = match[1].split(",").map((n) => parseInt(n.trim(), 10));
|
|
699
|
-
return Buffer.from(secretArray);
|
|
700
|
-
}
|
|
701
|
-
static transformSecret(buffer) {
|
|
702
|
-
const transformed = Buffer.alloc(buffer.length);
|
|
703
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
704
|
-
transformed[i] = buffer[i] ^ ((i % 33) + 9);
|
|
705
|
-
}
|
|
706
|
-
return transformed;
|
|
707
|
-
}
|
|
708
|
-
static generateTotp(secretBuffer) {
|
|
709
|
-
const period = 30; // seconds
|
|
710
|
-
const digits = 6;
|
|
711
|
-
const counter = Math.floor(Date.now() / 1000 / period);
|
|
661
|
+
static generateTotp() {
|
|
662
|
+
const TOTP_SECRET = new Uint8Array([
|
|
663
|
+
53, 53, 48, 55, 49, 52, 53, 56, 53, 51, 52, 56, 55, 52, 57, 57, 53, 57, 50, 50, 52, 56, 54, 51, 48, 51, 50, 57, 51, 52, 55,
|
|
664
|
+
]);
|
|
665
|
+
const hmac = crypto_1.default.createHmac("sha1", TOTP_SECRET);
|
|
666
|
+
const counter = Math.floor(Date.now() / 30000);
|
|
712
667
|
const counterBuffer = Buffer.alloc(8);
|
|
713
668
|
counterBuffer.writeBigInt64BE(BigInt(counter));
|
|
714
|
-
const hmac = crypto_1.default.createHmac("sha1", secretBuffer);
|
|
715
669
|
hmac.update(counterBuffer);
|
|
716
670
|
const hmacResult = hmac.digest();
|
|
717
|
-
const offset = hmacResult[hmacResult.length - 1] &
|
|
718
|
-
const
|
|
719
|
-
const
|
|
720
|
-
return
|
|
671
|
+
const offset = hmacResult[hmacResult.length - 1] & 15;
|
|
672
|
+
const truncatedValue = ((hmacResult[offset] & 127) << 24) | ((hmacResult[offset + 1] & 255) << 16) | ((hmacResult[offset + 2] & 255) << 8) | (hmacResult[offset + 3] & 255);
|
|
673
|
+
const totp = (truncatedValue % 1000000).toString().padStart(6, "0");
|
|
674
|
+
return [totp, counter * 30000];
|
|
721
675
|
}
|
|
722
676
|
}
|
|
723
677
|
exports.AutoPlayUtils = AutoPlayUtils;
|
package/package.json
CHANGED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// THIS WILL BE REMOVED IF YOU DONT FIND A USE FOR IT.
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.CollectionPlayerStore = void 0;
|
|
5
|
-
const collection_1 = require("@discordjs/collection");
|
|
6
|
-
class CollectionPlayerStore {
|
|
7
|
-
store = new collection_1.Collection();
|
|
8
|
-
async get(guildId) {
|
|
9
|
-
return this.store.get(guildId);
|
|
10
|
-
}
|
|
11
|
-
async set(guildId, player) {
|
|
12
|
-
this.store.set(guildId, player);
|
|
13
|
-
}
|
|
14
|
-
async delete(guildId) {
|
|
15
|
-
this.store.delete(guildId);
|
|
16
|
-
}
|
|
17
|
-
async keys() {
|
|
18
|
-
return [...this.store.keys()];
|
|
19
|
-
}
|
|
20
|
-
async has(guildId) {
|
|
21
|
-
return this.store.has(guildId);
|
|
22
|
-
}
|
|
23
|
-
async filter(predicate) {
|
|
24
|
-
const result = new Map();
|
|
25
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
26
|
-
if (await predicate(player, guildId)) {
|
|
27
|
-
result.set(guildId, player);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
async find(predicate) {
|
|
33
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
34
|
-
if (await predicate(player, guildId))
|
|
35
|
-
return player;
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
async map(callback) {
|
|
40
|
-
const results = [];
|
|
41
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
42
|
-
results.push(await callback(player, guildId));
|
|
43
|
-
}
|
|
44
|
-
return results;
|
|
45
|
-
}
|
|
46
|
-
async forEach(callback) {
|
|
47
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
48
|
-
await callback(player, guildId);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async some(predicate) {
|
|
52
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
53
|
-
if (await predicate(player, guildId))
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
async every(predicate) {
|
|
59
|
-
for (const [guildId, player] of this.store.entries()) {
|
|
60
|
-
if (!(await predicate(player, guildId)))
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
async size() {
|
|
66
|
-
return this.store.size;
|
|
67
|
-
}
|
|
68
|
-
async clear() {
|
|
69
|
-
this.store.clear();
|
|
70
|
-
}
|
|
71
|
-
async *entries() {
|
|
72
|
-
for (const entry of this.store.entries()) {
|
|
73
|
-
yield entry;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
exports.CollectionPlayerStore = CollectionPlayerStore;
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// THIS WILL BE REMOVED IF YOU DONT FIND A USE FOR IT.
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.RedisPlayerStore = void 0;
|
|
5
|
-
class RedisPlayerStore {
|
|
6
|
-
redis;
|
|
7
|
-
manager;
|
|
8
|
-
prefix;
|
|
9
|
-
constructor(redis, manager, prefix = "magmastream:") {
|
|
10
|
-
this.redis = redis;
|
|
11
|
-
this.manager = manager;
|
|
12
|
-
this.prefix = prefix;
|
|
13
|
-
}
|
|
14
|
-
getKey(guildId) {
|
|
15
|
-
return `${this.prefix}player:${guildId}`;
|
|
16
|
-
}
|
|
17
|
-
async get(guildId) {
|
|
18
|
-
const raw = await this.redis.get(this.getKey(guildId));
|
|
19
|
-
if (!raw)
|
|
20
|
-
return undefined;
|
|
21
|
-
return JSON.parse(raw);
|
|
22
|
-
}
|
|
23
|
-
async set(guildId, player) {
|
|
24
|
-
const serialized = this.manager.serializePlayer(player);
|
|
25
|
-
await this.redis.set(this.getKey(guildId), JSON.stringify(serialized));
|
|
26
|
-
}
|
|
27
|
-
async delete(guildId) {
|
|
28
|
-
await this.redis.del(this.getKey(guildId));
|
|
29
|
-
}
|
|
30
|
-
async keys() {
|
|
31
|
-
const keys = await this.redis.keys(`${this.prefix}player:*`);
|
|
32
|
-
return keys.map((key) => key.replace(`${this.prefix}player:`, ""));
|
|
33
|
-
}
|
|
34
|
-
async has(guildId) {
|
|
35
|
-
return (await this.redis.exists(this.getKey(guildId))) === 1;
|
|
36
|
-
}
|
|
37
|
-
async filter(predicate) {
|
|
38
|
-
const keys = await this.keys();
|
|
39
|
-
const pipeline = this.redis.pipeline();
|
|
40
|
-
for (const guildId of keys) {
|
|
41
|
-
pipeline.get(this.getKey(guildId));
|
|
42
|
-
}
|
|
43
|
-
const results = await pipeline.exec();
|
|
44
|
-
const result = new Map();
|
|
45
|
-
for (let i = 0; i < results.length; i++) {
|
|
46
|
-
const [err, raw] = results[i];
|
|
47
|
-
if (err || typeof raw !== "string")
|
|
48
|
-
continue;
|
|
49
|
-
const guildId = keys[i];
|
|
50
|
-
const player = JSON.parse(raw);
|
|
51
|
-
if (await predicate(player, guildId)) {
|
|
52
|
-
result.set(guildId, player);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
async find(predicate) {
|
|
58
|
-
for (const guildId of await this.keys()) {
|
|
59
|
-
const raw = await this.redis.get(this.getKey(guildId));
|
|
60
|
-
if (!raw)
|
|
61
|
-
continue;
|
|
62
|
-
const parsed = JSON.parse(raw);
|
|
63
|
-
if (await predicate(parsed, guildId))
|
|
64
|
-
return parsed;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
async map(callback) {
|
|
69
|
-
const keys = await this.keys();
|
|
70
|
-
if (!keys.length)
|
|
71
|
-
return [];
|
|
72
|
-
const pipeline = this.redis.pipeline();
|
|
73
|
-
for (const guildId of keys) {
|
|
74
|
-
pipeline.get(this.getKey(guildId));
|
|
75
|
-
}
|
|
76
|
-
const results = await pipeline.exec();
|
|
77
|
-
const output = [];
|
|
78
|
-
for (let i = 0; i < results.length; i++) {
|
|
79
|
-
const [err, raw] = results[i];
|
|
80
|
-
if (err || typeof raw !== "string")
|
|
81
|
-
continue;
|
|
82
|
-
const guildId = keys[i];
|
|
83
|
-
const player = JSON.parse(raw);
|
|
84
|
-
output.push(await callback(player, guildId));
|
|
85
|
-
}
|
|
86
|
-
return output;
|
|
87
|
-
}
|
|
88
|
-
async forEach(callback) {
|
|
89
|
-
for (const guildId of await this.keys()) {
|
|
90
|
-
const raw = await this.redis.get(this.getKey(guildId));
|
|
91
|
-
if (!raw)
|
|
92
|
-
continue;
|
|
93
|
-
const parsed = JSON.parse(raw);
|
|
94
|
-
await callback(parsed, guildId);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async some(predicate) {
|
|
98
|
-
const keys = await this.keys();
|
|
99
|
-
if (!keys.length)
|
|
100
|
-
return false;
|
|
101
|
-
const pipeline = this.redis.pipeline();
|
|
102
|
-
for (const guildId of keys) {
|
|
103
|
-
pipeline.get(this.getKey(guildId));
|
|
104
|
-
}
|
|
105
|
-
const results = await pipeline.exec();
|
|
106
|
-
for (let i = 0; i < results.length; i++) {
|
|
107
|
-
const [err, raw] = results[i];
|
|
108
|
-
if (err || typeof raw !== "string")
|
|
109
|
-
continue;
|
|
110
|
-
const guildId = keys[i];
|
|
111
|
-
const player = JSON.parse(raw);
|
|
112
|
-
if (await predicate(player, guildId))
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
async every(predicate) {
|
|
118
|
-
const keys = await this.keys();
|
|
119
|
-
if (!keys.length)
|
|
120
|
-
return true;
|
|
121
|
-
const pipeline = this.redis.pipeline();
|
|
122
|
-
for (const guildId of keys) {
|
|
123
|
-
pipeline.get(this.getKey(guildId));
|
|
124
|
-
}
|
|
125
|
-
const results = await pipeline.exec();
|
|
126
|
-
for (let i = 0; i < results.length; i++) {
|
|
127
|
-
const [err, raw] = results[i];
|
|
128
|
-
if (err || typeof raw !== "string")
|
|
129
|
-
continue;
|
|
130
|
-
const guildId = keys[i];
|
|
131
|
-
const player = JSON.parse(raw);
|
|
132
|
-
if (!(await predicate(player, guildId)))
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
async size() {
|
|
138
|
-
const keys = await this.keys();
|
|
139
|
-
return keys.length;
|
|
140
|
-
}
|
|
141
|
-
async clear() {
|
|
142
|
-
const keys = await this.redis.keys(`${this.prefix}player:*`);
|
|
143
|
-
if (keys.length) {
|
|
144
|
-
await this.redis.del(...keys);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async *entries() {
|
|
148
|
-
for (const guildId of await this.keys()) {
|
|
149
|
-
const raw = await this.redis.get(this.getKey(guildId));
|
|
150
|
-
if (!raw)
|
|
151
|
-
continue;
|
|
152
|
-
yield [guildId, JSON.parse(raw)];
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
exports.RedisPlayerStore = RedisPlayerStore;
|