hoshimi 0.2.0
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/README.md +139 -0
- package/dist/index.d.mts +5787 -0
- package/dist/index.d.ts +5787 -0
- package/dist/index.js +4577 -0
- package/dist/index.mjs +4510 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4577 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AudioOutput: () => AudioOutput,
|
|
24
|
+
DSPXPluginFilter: () => DSPXPluginFilter,
|
|
25
|
+
DebugLevels: () => DebugLevels,
|
|
26
|
+
DestroyReasons: () => DestroyReasons,
|
|
27
|
+
Events: () => Events,
|
|
28
|
+
FilterManager: () => FilterManager,
|
|
29
|
+
FilterType: () => FilterType,
|
|
30
|
+
Hoshimi: () => Hoshimi,
|
|
31
|
+
HttpMethods: () => HttpMethods,
|
|
32
|
+
HttpStatusCodes: () => HttpStatusCodes,
|
|
33
|
+
LavalinkPluginFilter: () => LavalinkPluginFilter,
|
|
34
|
+
LoadType: () => LoadType,
|
|
35
|
+
LoopMode: () => LoopMode,
|
|
36
|
+
ManagerError: () => ManagerError,
|
|
37
|
+
MemoryAdapter: () => MemoryAdapter,
|
|
38
|
+
Node: () => Node,
|
|
39
|
+
NodeDestroyReasons: () => NodeDestroyReasons,
|
|
40
|
+
NodeError: () => NodeError,
|
|
41
|
+
NodeSortTypes: () => NodeSortTypes,
|
|
42
|
+
OpCodes: () => OpCodes,
|
|
43
|
+
OptionError: () => OptionError,
|
|
44
|
+
Player: () => Player,
|
|
45
|
+
PlayerError: () => PlayerError,
|
|
46
|
+
PlayerEventType: () => PlayerEventType,
|
|
47
|
+
PluginInfoType: () => PluginInfoType,
|
|
48
|
+
PluginNames: () => PluginNames,
|
|
49
|
+
Queue: () => Queue,
|
|
50
|
+
ResolveError: () => ResolveError,
|
|
51
|
+
Rest: () => Rest,
|
|
52
|
+
SearchEngines: () => SearchEngines,
|
|
53
|
+
Severity: () => Severity,
|
|
54
|
+
SourceNames: () => SourceNames,
|
|
55
|
+
State: () => State,
|
|
56
|
+
StorageAdapter: () => StorageAdapter,
|
|
57
|
+
StorageError: () => StorageError,
|
|
58
|
+
Structures: () => Structures,
|
|
59
|
+
Track: () => Track,
|
|
60
|
+
TrackEndReason: () => TrackEndReason,
|
|
61
|
+
UnresolvedTrack: () => UnresolvedTrack,
|
|
62
|
+
WebsocketCloseCodes: () => WebsocketCloseCodes,
|
|
63
|
+
createHoshimi: () => createHoshimi
|
|
64
|
+
});
|
|
65
|
+
module.exports = __toCommonJS(index_exports);
|
|
66
|
+
|
|
67
|
+
// src/types/Node.ts
|
|
68
|
+
var State = /* @__PURE__ */ ((State2) => {
|
|
69
|
+
State2[State2["Connecting"] = 1] = "Connecting";
|
|
70
|
+
State2[State2["Connected"] = 2] = "Connected";
|
|
71
|
+
State2[State2["Disconnected"] = 3] = "Disconnected";
|
|
72
|
+
State2[State2["Reconnecting"] = 4] = "Reconnecting";
|
|
73
|
+
State2[State2["Reconnected"] = 5] = "Reconnected";
|
|
74
|
+
State2[State2["Destroyed"] = 6] = "Destroyed";
|
|
75
|
+
State2[State2["Idle"] = 7] = "Idle";
|
|
76
|
+
return State2;
|
|
77
|
+
})(State || {});
|
|
78
|
+
var OpCodes = /* @__PURE__ */ ((OpCodes2) => {
|
|
79
|
+
OpCodes2["Ready"] = "ready";
|
|
80
|
+
OpCodes2["PlayerUpdate"] = "playerUpdate";
|
|
81
|
+
OpCodes2["Stats"] = "stats";
|
|
82
|
+
OpCodes2["Event"] = "event";
|
|
83
|
+
return OpCodes2;
|
|
84
|
+
})(OpCodes || {});
|
|
85
|
+
var LoadType = /* @__PURE__ */ ((LoadType2) => {
|
|
86
|
+
LoadType2["Track"] = "track";
|
|
87
|
+
LoadType2["Playlist"] = "playlist";
|
|
88
|
+
LoadType2["Search"] = "search";
|
|
89
|
+
LoadType2["Empty"] = "empty";
|
|
90
|
+
LoadType2["Error"] = "error";
|
|
91
|
+
return LoadType2;
|
|
92
|
+
})(LoadType || {});
|
|
93
|
+
var SourceNames = /* @__PURE__ */ ((SourceNames2) => {
|
|
94
|
+
SourceNames2["Youtube"] = "youtube";
|
|
95
|
+
SourceNames2["YoutubeMusic"] = "youtubemusic";
|
|
96
|
+
SourceNames2["Soundcloud"] = "soundcloud";
|
|
97
|
+
SourceNames2["Bandcamp"] = "bandcamp";
|
|
98
|
+
SourceNames2["Twitch"] = "twitch";
|
|
99
|
+
SourceNames2["Vimeo"] = "vimeo";
|
|
100
|
+
SourceNames2["Mixer"] = "mixer";
|
|
101
|
+
SourceNames2["Spotify"] = "spotify";
|
|
102
|
+
SourceNames2["Deezer"] = "deezer";
|
|
103
|
+
SourceNames2["VKMusic"] = "vkmusic";
|
|
104
|
+
SourceNames2["Tidal"] = "tidal";
|
|
105
|
+
SourceNames2["JioSaavn"] = "jiosaavn";
|
|
106
|
+
SourceNames2["AppleMusic"] = "applemusic";
|
|
107
|
+
SourceNames2["YandexMusic"] = "yandexmusic";
|
|
108
|
+
SourceNames2["FloweryTTS"] = "flowery-tts";
|
|
109
|
+
SourceNames2["HTTP"] = "http";
|
|
110
|
+
SourceNames2["PornHub"] = "pornhub";
|
|
111
|
+
SourceNames2["TextToSpeech"] = "tts";
|
|
112
|
+
SourceNames2["Clypit"] = "clypit";
|
|
113
|
+
SourceNames2["StreamDeckAudio"] = "StreamDeckAudio";
|
|
114
|
+
SourceNames2["GetYarn"] = "getyarn.io";
|
|
115
|
+
SourceNames2["MixCloud"] = "mixcloud";
|
|
116
|
+
SourceNames2["OCRemix"] = "ocremix";
|
|
117
|
+
SourceNames2["PixelDrain"] = "pixeldrain";
|
|
118
|
+
SourceNames2["Reddit"] = "reddit";
|
|
119
|
+
SourceNames2["SoundGasm"] = "soundgasm";
|
|
120
|
+
SourceNames2["TikTok"] = "tiktok";
|
|
121
|
+
return SourceNames2;
|
|
122
|
+
})(SourceNames || {});
|
|
123
|
+
var Severity = /* @__PURE__ */ ((Severity2) => {
|
|
124
|
+
Severity2["Common"] = "common";
|
|
125
|
+
Severity2["Suspicious"] = "suspicious";
|
|
126
|
+
Severity2["Fault"] = "fault";
|
|
127
|
+
return Severity2;
|
|
128
|
+
})(Severity || {});
|
|
129
|
+
var PluginInfoType = /* @__PURE__ */ ((PluginInfoType2) => {
|
|
130
|
+
PluginInfoType2["Album"] = "album";
|
|
131
|
+
PluginInfoType2["Playlist"] = "playlist";
|
|
132
|
+
PluginInfoType2["Artist"] = "artist";
|
|
133
|
+
PluginInfoType2["Recommendations"] = "recommendations";
|
|
134
|
+
return PluginInfoType2;
|
|
135
|
+
})(PluginInfoType || {});
|
|
136
|
+
var NodeDestroyReasons = /* @__PURE__ */ ((NodeDestroyReasons2) => {
|
|
137
|
+
NodeDestroyReasons2["Destroy"] = "Node-Destroy";
|
|
138
|
+
NodeDestroyReasons2["MissingSession"] = "Missing-Session";
|
|
139
|
+
return NodeDestroyReasons2;
|
|
140
|
+
})(NodeDestroyReasons || {});
|
|
141
|
+
var WebsocketCloseCodes = /* @__PURE__ */ ((WebsocketCloseCodes2) => {
|
|
142
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["NormalClosure"] = 1e3] = "NormalClosure";
|
|
143
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["GoingAway"] = 1001] = "GoingAway";
|
|
144
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["ProtocolError"] = 1002] = "ProtocolError";
|
|
145
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["UnsupportedData"] = 1003] = "UnsupportedData";
|
|
146
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["NoStatusReceived"] = 1005] = "NoStatusReceived";
|
|
147
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["AbnormalClosure"] = 1006] = "AbnormalClosure";
|
|
148
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["InvalidFramePayloadData"] = 1007] = "InvalidFramePayloadData";
|
|
149
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["PolicyViolation"] = 1008] = "PolicyViolation";
|
|
150
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MessageTooBig"] = 1009] = "MessageTooBig";
|
|
151
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["MandatoryExtension"] = 1010] = "MandatoryExtension";
|
|
152
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["InternalError"] = 1011] = "InternalError";
|
|
153
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["ServiceRestart"] = 1012] = "ServiceRestart";
|
|
154
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["TryAgainLater"] = 1013] = "TryAgainLater";
|
|
155
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["BadGateway"] = 1014] = "BadGateway";
|
|
156
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["TLSHandshakeFailure"] = 1015] = "TLSHandshakeFailure";
|
|
157
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["Unauthorized"] = 3e3] = "Unauthorized";
|
|
158
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["Forbidden"] = 3003] = "Forbidden";
|
|
159
|
+
WebsocketCloseCodes2[WebsocketCloseCodes2["Timeout"] = 3008] = "Timeout";
|
|
160
|
+
return WebsocketCloseCodes2;
|
|
161
|
+
})(WebsocketCloseCodes || {});
|
|
162
|
+
var PluginNames = /* @__PURE__ */ ((PluginNames2) => {
|
|
163
|
+
PluginNames2["LavaSrc"] = "lavasrc-plugin";
|
|
164
|
+
PluginNames2["JavaLyrics"] = "java-lyrics-plugin";
|
|
165
|
+
PluginNames2["LavaLyrics"] = "lavalyrics-plugin";
|
|
166
|
+
PluginNames2["LavaSearch"] = "lavasearch-plugin";
|
|
167
|
+
PluginNames2["SponsorBlock"] = "sponsorblock-plugin";
|
|
168
|
+
PluginNames2["LavaDspx"] = "lavadspx-plugin";
|
|
169
|
+
PluginNames2["Youtube"] = "youtube-plugin";
|
|
170
|
+
PluginNames2["Skybot"] = "skybot-lavalink-plugin";
|
|
171
|
+
PluginNames2["LavaXm"] = "lava-xm-plugin";
|
|
172
|
+
PluginNames2["Jiosaavn"] = "jiosaavn-plugin";
|
|
173
|
+
PluginNames2["FilterPlugin"] = "lavalink-filter-plugin";
|
|
174
|
+
return PluginNames2;
|
|
175
|
+
})(PluginNames || {});
|
|
176
|
+
var NodeSortTypes = /* @__PURE__ */ ((NodeSortTypes2) => {
|
|
177
|
+
NodeSortTypes2["Memory"] = "memory";
|
|
178
|
+
NodeSortTypes2["Cpu"] = "cpu";
|
|
179
|
+
NodeSortTypes2["Players"] = "players";
|
|
180
|
+
NodeSortTypes2["PlayingPlayers"] = "playingPlayers";
|
|
181
|
+
NodeSortTypes2["SystemLoad"] = "systemLoad";
|
|
182
|
+
NodeSortTypes2["LavalinkLoad"] = "lavalinkLoad";
|
|
183
|
+
return NodeSortTypes2;
|
|
184
|
+
})(NodeSortTypes || {});
|
|
185
|
+
|
|
186
|
+
// src/classes/Errors.ts
|
|
187
|
+
var ManagerError = class extends Error {
|
|
188
|
+
constructor(message) {
|
|
189
|
+
super(message);
|
|
190
|
+
this.name = "Hoshimi [ManagerError]";
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var OptionError = class extends Error {
|
|
194
|
+
constructor(message) {
|
|
195
|
+
super(message);
|
|
196
|
+
this.name = "Hoshimi [OptionError]";
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
var PlayerError = class extends Error {
|
|
200
|
+
constructor(message) {
|
|
201
|
+
super(message);
|
|
202
|
+
this.name = "Hoshimi [PlayerError]";
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var NodeError = class extends Error {
|
|
206
|
+
constructor({ message, id }) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.name = `Hoshimi [NodeError | ${id}]`;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var StorageError = class extends Error {
|
|
212
|
+
constructor(message) {
|
|
213
|
+
super(message);
|
|
214
|
+
this.name = "Hoshimi [StorageError]";
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var ResolveError = class extends Error {
|
|
218
|
+
constructor(message) {
|
|
219
|
+
super(message);
|
|
220
|
+
this.name = "Hoshimi [ResolveError]";
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/types/Manager.ts
|
|
225
|
+
var SearchEngines = /* @__PURE__ */ ((SearchEngines2) => {
|
|
226
|
+
SearchEngines2["Youtube"] = "ytsearch";
|
|
227
|
+
SearchEngines2["YoutubeMusic"] = "ytmsearch";
|
|
228
|
+
SearchEngines2["Spotify"] = "spsearch";
|
|
229
|
+
SearchEngines2["SpotifyRecommendations"] = "sprec";
|
|
230
|
+
SearchEngines2["SpotifyArtistMix"] = "sprec:mix:artist";
|
|
231
|
+
SearchEngines2["SpotifyAlbumMix"] = "sprec:mix:album";
|
|
232
|
+
SearchEngines2["SpotifyTrackMix"] = "sprec:mix:track";
|
|
233
|
+
SearchEngines2["SpotifyISRCMix"] = "sprec:mix:isrc";
|
|
234
|
+
SearchEngines2["SoundCloud"] = "scsearch";
|
|
235
|
+
SearchEngines2["AppleMusic"] = "amsearch";
|
|
236
|
+
SearchEngines2["BandCamp"] = "bcsearch";
|
|
237
|
+
SearchEngines2["Deezer"] = "dzsearch";
|
|
238
|
+
SearchEngines2["DeezerISRC"] = "dzisrc";
|
|
239
|
+
SearchEngines2["DeezerRecommendations"] = "dzrec";
|
|
240
|
+
SearchEngines2["YandexMusic"] = "ymsearch";
|
|
241
|
+
SearchEngines2["YandexMusicRecommendations"] = "ymrec";
|
|
242
|
+
SearchEngines2["VKMusic"] = "vksearch";
|
|
243
|
+
SearchEngines2["VKMusicRecommendations"] = "vkrec";
|
|
244
|
+
SearchEngines2["Tidal"] = "tdsearch";
|
|
245
|
+
SearchEngines2["TidalRecommendations"] = "tdrec";
|
|
246
|
+
SearchEngines2["Qobuz"] = "qbsearch";
|
|
247
|
+
SearchEngines2["QobuzISRC"] = "qbisrc";
|
|
248
|
+
SearchEngines2["QobuzRecommendations"] = "qbrec";
|
|
249
|
+
SearchEngines2["JioSaavn"] = "jssearch";
|
|
250
|
+
SearchEngines2["JioSaavnRecommendations"] = "jsrec";
|
|
251
|
+
SearchEngines2["Twitch"] = "twsearch";
|
|
252
|
+
SearchEngines2["Mixer"] = "mxsearch";
|
|
253
|
+
SearchEngines2["Vimeo"] = "vmsearch";
|
|
254
|
+
SearchEngines2["FloweryTTS"] = "ftts";
|
|
255
|
+
SearchEngines2["Local"] = "local";
|
|
256
|
+
SearchEngines2["PornHub"] = "phsearch";
|
|
257
|
+
SearchEngines2["TextToSpeech"] = "speak";
|
|
258
|
+
return SearchEngines2;
|
|
259
|
+
})(SearchEngines || {});
|
|
260
|
+
var DebugLevels = /* @__PURE__ */ ((DebugLevels2) => {
|
|
261
|
+
DebugLevels2[DebugLevels2["Manager"] = 1] = "Manager";
|
|
262
|
+
DebugLevels2[DebugLevels2["Node"] = 2] = "Node";
|
|
263
|
+
DebugLevels2[DebugLevels2["Player"] = 3] = "Player";
|
|
264
|
+
DebugLevels2[DebugLevels2["Rest"] = 4] = "Rest";
|
|
265
|
+
DebugLevels2[DebugLevels2["Queue"] = 5] = "Queue";
|
|
266
|
+
DebugLevels2[DebugLevels2["Test"] = 6] = "Test";
|
|
267
|
+
return DebugLevels2;
|
|
268
|
+
})(DebugLevels || {});
|
|
269
|
+
var Events = /* @__PURE__ */ ((Events2) => {
|
|
270
|
+
Events2["Debug"] = "debug";
|
|
271
|
+
Events2["Error"] = "error";
|
|
272
|
+
Events2["NodeRaw"] = "nodeRaw";
|
|
273
|
+
Events2["NodeError"] = "nodeError";
|
|
274
|
+
Events2["NodeReady"] = "nodeReady";
|
|
275
|
+
Events2["NodeDisconnect"] = "nodeDisconnect";
|
|
276
|
+
Events2["NodeReconnecting"] = "nodeReconnecting";
|
|
277
|
+
Events2["NodeDestroy"] = "nodeDestroy";
|
|
278
|
+
Events2["NodeResumed"] = "nodeResumed";
|
|
279
|
+
Events2["NodeCreate"] = "nodeCreate";
|
|
280
|
+
Events2["PlayerCreate"] = "playerCreate";
|
|
281
|
+
Events2["PlayerUpdate"] = "playerUpdate";
|
|
282
|
+
Events2["PlayerDestroy"] = "playerDestroy";
|
|
283
|
+
Events2["TrackStart"] = "trackStart";
|
|
284
|
+
Events2["TrackEnd"] = "trackEnd";
|
|
285
|
+
Events2["TrackStuck"] = "trackStuck";
|
|
286
|
+
Events2["TrackError"] = "trackError";
|
|
287
|
+
Events2["LyricsFound"] = "lyricsFound";
|
|
288
|
+
Events2["LyricsNotFound"] = "lyricsNotFound";
|
|
289
|
+
Events2["LyricsLine"] = "lyricsLine";
|
|
290
|
+
Events2["QueueEnd"] = "queueEnd";
|
|
291
|
+
Events2["QueueUpdate"] = "queueUpdate";
|
|
292
|
+
Events2["WebSocketClosed"] = "socketClosed";
|
|
293
|
+
return Events2;
|
|
294
|
+
})(Events || {});
|
|
295
|
+
var DestroyReasons = /* @__PURE__ */ ((DestroyReasons2) => {
|
|
296
|
+
DestroyReasons2["Stop"] = "Player-Stop";
|
|
297
|
+
DestroyReasons2["Requested"] = "Player-Requested";
|
|
298
|
+
DestroyReasons2["Empty"] = "Player-Empty";
|
|
299
|
+
DestroyReasons2["NodeDisconnected"] = "Player-NodeDisconnected";
|
|
300
|
+
DestroyReasons2["NodeDestroy"] = "Player-NodeDestroy";
|
|
301
|
+
DestroyReasons2["VoiceChannelDeleted"] = "Player-VoiceChannelDeleted";
|
|
302
|
+
return DestroyReasons2;
|
|
303
|
+
})(DestroyReasons || {});
|
|
304
|
+
|
|
305
|
+
// src/types/Player.ts
|
|
306
|
+
var LoopMode = /* @__PURE__ */ ((LoopMode2) => {
|
|
307
|
+
LoopMode2[LoopMode2["Track"] = 1] = "Track";
|
|
308
|
+
LoopMode2[LoopMode2["Queue"] = 2] = "Queue";
|
|
309
|
+
LoopMode2[LoopMode2["Off"] = 3] = "Off";
|
|
310
|
+
return LoopMode2;
|
|
311
|
+
})(LoopMode || {});
|
|
312
|
+
var PlayerEventType = /* @__PURE__ */ ((PlayerEventType2) => {
|
|
313
|
+
PlayerEventType2["TrackStart"] = "TrackStartEvent";
|
|
314
|
+
PlayerEventType2["TrackEnd"] = "TrackEndEvent";
|
|
315
|
+
PlayerEventType2["TrackException"] = "TrackExceptionEvent";
|
|
316
|
+
PlayerEventType2["TrackStuck"] = "TrackStuckEvent";
|
|
317
|
+
PlayerEventType2["LyricsFound"] = "LyricsFoundEvent";
|
|
318
|
+
PlayerEventType2["LyricsNotFound"] = "LyricsNotFoundEvent";
|
|
319
|
+
PlayerEventType2["LyricsLine"] = "LyricsLineEvent";
|
|
320
|
+
PlayerEventType2["WebsocketClosed"] = "WebSocketClosedEvent";
|
|
321
|
+
return PlayerEventType2;
|
|
322
|
+
})(PlayerEventType || {});
|
|
323
|
+
var TrackEndReason = /* @__PURE__ */ ((TrackEndReason2) => {
|
|
324
|
+
TrackEndReason2["Finished"] = "finished";
|
|
325
|
+
TrackEndReason2["LoadFailed"] = "loadFailed";
|
|
326
|
+
TrackEndReason2["Stopped"] = "stopped";
|
|
327
|
+
TrackEndReason2["Replaced"] = "replaced";
|
|
328
|
+
TrackEndReason2["Cleanup"] = "cleanup";
|
|
329
|
+
return TrackEndReason2;
|
|
330
|
+
})(TrackEndReason || {});
|
|
331
|
+
|
|
332
|
+
// package.json
|
|
333
|
+
var package_default = {
|
|
334
|
+
name: "hoshimi",
|
|
335
|
+
version: "0.2.0",
|
|
336
|
+
description: "A lavalink@v4 client easy to use, up-to-date and all ears.",
|
|
337
|
+
main: "./dist/index.js",
|
|
338
|
+
module: "./dist/index.mjs",
|
|
339
|
+
types: "./dist/index.d.ts",
|
|
340
|
+
packageManager: "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd",
|
|
341
|
+
files: [
|
|
342
|
+
"dist"
|
|
343
|
+
],
|
|
344
|
+
"lint-staged": {
|
|
345
|
+
"*.{ts,json}": [
|
|
346
|
+
"pnpm format"
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
repository: {
|
|
350
|
+
url: "https://github.com/Ganyu-Studios/Hoshimi.git",
|
|
351
|
+
type: "git"
|
|
352
|
+
},
|
|
353
|
+
scripts: {
|
|
354
|
+
build: "pnpm typecheck && tsup",
|
|
355
|
+
typecheck: "tsc --noEmit",
|
|
356
|
+
lint: "biome lint .",
|
|
357
|
+
format: "biome format --write .",
|
|
358
|
+
prepare: "husky && pnpm build",
|
|
359
|
+
"tool:prepublish": "pnpm build && tsx ./scripts/prepublish.mts && cd .npm && npm pack",
|
|
360
|
+
"tool:publish": "cd .npm && npm publish"
|
|
361
|
+
},
|
|
362
|
+
keywords: [
|
|
363
|
+
"discord",
|
|
364
|
+
"lavalink",
|
|
365
|
+
"lavalink-v4",
|
|
366
|
+
"discord",
|
|
367
|
+
"distube",
|
|
368
|
+
"music-bot",
|
|
369
|
+
"lavalink-player",
|
|
370
|
+
"lavalink-client",
|
|
371
|
+
"music"
|
|
372
|
+
],
|
|
373
|
+
author: "Ganyu Studios",
|
|
374
|
+
license: "MIT",
|
|
375
|
+
devDependencies: {
|
|
376
|
+
"@biomejs/biome": "^2.3.2",
|
|
377
|
+
"@types/node": "^24.9.2",
|
|
378
|
+
"@types/ws": "^8.18.1",
|
|
379
|
+
husky: "^9.1.7",
|
|
380
|
+
"lint-staged": "^16.2.6",
|
|
381
|
+
tsup: "^8.5.0",
|
|
382
|
+
tsx: "^4.20.6",
|
|
383
|
+
typescript: "^5.9.3"
|
|
384
|
+
},
|
|
385
|
+
dependencies: {
|
|
386
|
+
ws: "^8.18.3"
|
|
387
|
+
},
|
|
388
|
+
pnpm: {
|
|
389
|
+
onlyBuiltDependencies: [
|
|
390
|
+
"esbuild"
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/types/Filters.ts
|
|
396
|
+
var AudioOutput = /* @__PURE__ */ ((AudioOutput2) => {
|
|
397
|
+
AudioOutput2["Mono"] = "mono";
|
|
398
|
+
AudioOutput2["Stereo"] = "stereo";
|
|
399
|
+
AudioOutput2["Left"] = "left";
|
|
400
|
+
AudioOutput2["Right"] = "right";
|
|
401
|
+
return AudioOutput2;
|
|
402
|
+
})(AudioOutput || {});
|
|
403
|
+
var FilterType = /* @__PURE__ */ ((FilterType2) => {
|
|
404
|
+
FilterType2["Volume"] = "volume";
|
|
405
|
+
FilterType2["AudioOutput"] = "audioOutput";
|
|
406
|
+
FilterType2["LowPass"] = "lowPass";
|
|
407
|
+
FilterType2["Karaoke"] = "karaoke";
|
|
408
|
+
FilterType2["Rotation"] = "rotation";
|
|
409
|
+
FilterType2["Tremolo"] = "tremolo";
|
|
410
|
+
FilterType2["Vibrato"] = "vibrato";
|
|
411
|
+
FilterType2["Custom"] = "custom";
|
|
412
|
+
FilterType2["Timescale"] = "timescale";
|
|
413
|
+
FilterType2["Distortion"] = "distortion";
|
|
414
|
+
FilterType2["Echo"] = "echo";
|
|
415
|
+
FilterType2["Reverb"] = "reverb";
|
|
416
|
+
FilterType2["DSPXLowpass"] = "low-pass";
|
|
417
|
+
FilterType2["DSPXHighpass"] = "high-pass";
|
|
418
|
+
FilterType2["DSPXEcho"] = "echo";
|
|
419
|
+
FilterType2["DSPXNormalization"] = "normalization";
|
|
420
|
+
return FilterType2;
|
|
421
|
+
})(FilterType || {});
|
|
422
|
+
|
|
423
|
+
// src/util/constants.ts
|
|
424
|
+
var HoshimiAgent = `hoshimi/v${package_default.version} (${package_default.repository.url})`;
|
|
425
|
+
var UrlRegex = /^(https?:\/\/)?([a-zA-Z0-9\-_]+\.)+[a-zA-Z]{2,}(\/[^\s]*)?$/;
|
|
426
|
+
var ValidEngines = Object.values(SearchEngines);
|
|
427
|
+
var ValidSources = new Map(
|
|
428
|
+
Object.entries({
|
|
429
|
+
["youtube" /* Youtube */]: "ytsearch" /* Youtube */,
|
|
430
|
+
["youtubemusic" /* YoutubeMusic */]: "ytmsearch" /* YoutubeMusic */,
|
|
431
|
+
["soundcloud" /* Soundcloud */]: "scsearch" /* SoundCloud */,
|
|
432
|
+
["bandcamp" /* Bandcamp */]: "bcsearch" /* BandCamp */,
|
|
433
|
+
["twitch" /* Twitch */]: "twsearch" /* Twitch */,
|
|
434
|
+
["vimeo" /* Vimeo */]: "vmsearch" /* Vimeo */,
|
|
435
|
+
["mixer" /* Mixer */]: "mxsearch" /* Mixer */,
|
|
436
|
+
["spotify" /* Spotify */]: "spsearch" /* Spotify */,
|
|
437
|
+
["deezer" /* Deezer */]: "dzsearch" /* Deezer */,
|
|
438
|
+
["applemusic" /* AppleMusic */]: "amsearch" /* AppleMusic */,
|
|
439
|
+
["yandexmusic" /* YandexMusic */]: "ymsearch" /* YandexMusic */,
|
|
440
|
+
["flowery-tts" /* FloweryTTS */]: "ftts" /* FloweryTTS */,
|
|
441
|
+
["jiosaavn" /* JioSaavn */]: "jssearch" /* JioSaavn */,
|
|
442
|
+
["vkmusic" /* VKMusic */]: "vksearch" /* VKMusic */,
|
|
443
|
+
["tidal" /* Tidal */]: "tdsearch" /* Tidal */,
|
|
444
|
+
["tts" /* TextToSpeech */]: "speak" /* TextToSpeech */,
|
|
445
|
+
["pornhub" /* PornHub */]: "phsearch" /* PornHub */
|
|
446
|
+
})
|
|
447
|
+
);
|
|
448
|
+
var AudioOutputData = {
|
|
449
|
+
["mono" /* Mono */]: {
|
|
450
|
+
leftToLeft: 0.5,
|
|
451
|
+
leftToRight: 0.5,
|
|
452
|
+
rightToLeft: 0.5,
|
|
453
|
+
rightToRight: 0.5
|
|
454
|
+
},
|
|
455
|
+
["stereo" /* Stereo */]: {
|
|
456
|
+
leftToLeft: 1,
|
|
457
|
+
leftToRight: 0,
|
|
458
|
+
rightToLeft: 0,
|
|
459
|
+
rightToRight: 1
|
|
460
|
+
},
|
|
461
|
+
["left" /* Left */]: {
|
|
462
|
+
leftToLeft: 1,
|
|
463
|
+
leftToRight: 0,
|
|
464
|
+
rightToLeft: 1,
|
|
465
|
+
rightToRight: 0
|
|
466
|
+
},
|
|
467
|
+
["right" /* Right */]: {
|
|
468
|
+
leftToLeft: 0,
|
|
469
|
+
leftToRight: 1,
|
|
470
|
+
rightToLeft: 0,
|
|
471
|
+
rightToRight: 1
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var DefaultFilterPreset = {
|
|
475
|
+
Karaoke: { level: 1, monoLevel: 1, filterBand: 220, filterWidth: 100 },
|
|
476
|
+
Vaporwave: { speed: 0.8500000238418579, pitch: 0.800000011920929, rate: 1 },
|
|
477
|
+
Nightcore: { speed: 1.289999523162842, pitch: 1.289999523162842, rate: 0.9365999523162842 },
|
|
478
|
+
Lowpass: { smoothing: 20 },
|
|
479
|
+
Tremolo: { frequency: 4, depth: 0.8 },
|
|
480
|
+
Vibrato: { frequency: 4, depth: 0.8 },
|
|
481
|
+
Distortion: { cosOffset: 0.4, sinOffset: 0.4, tanOffset: 0.4, offset: 0.4, scale: 1.5, cosScale: 1.5, sinScale: 1.5, tanScale: 1.5 },
|
|
482
|
+
DSPXHighPass: { boostFactor: 1, cutoffFrequency: 80 },
|
|
483
|
+
DSPXLowPass: { boostFactor: 1, cutoffFrequency: 80 },
|
|
484
|
+
DSPXNormalization: { adaptive: true, maxAmplitude: 0.1 },
|
|
485
|
+
DSPXEcho: { decay: 0.5, echoLength: 0.5 },
|
|
486
|
+
PluginEcho: { decay: 0.8, delay: 4 },
|
|
487
|
+
PluginReverb: { delays: [0.037, 0.042, 0.048, 0.053], gains: [0.84, 0.83, 0.82, 0.81] }
|
|
488
|
+
};
|
|
489
|
+
var DefaultPlayerFilters = {
|
|
490
|
+
volume: 1,
|
|
491
|
+
lowPass: {
|
|
492
|
+
smoothing: 0
|
|
493
|
+
},
|
|
494
|
+
karaoke: {
|
|
495
|
+
level: 0,
|
|
496
|
+
monoLevel: 0,
|
|
497
|
+
filterBand: 0,
|
|
498
|
+
filterWidth: 0
|
|
499
|
+
},
|
|
500
|
+
timescale: {
|
|
501
|
+
speed: 1,
|
|
502
|
+
pitch: 1,
|
|
503
|
+
rate: 1
|
|
504
|
+
},
|
|
505
|
+
rotation: {
|
|
506
|
+
rotationHz: 0
|
|
507
|
+
},
|
|
508
|
+
tremolo: {
|
|
509
|
+
frequency: 0,
|
|
510
|
+
depth: 0
|
|
511
|
+
},
|
|
512
|
+
vibrato: {
|
|
513
|
+
frequency: 0,
|
|
514
|
+
depth: 0
|
|
515
|
+
},
|
|
516
|
+
pluginFilters: {
|
|
517
|
+
"high-pass": {
|
|
518
|
+
boostFactor: 0,
|
|
519
|
+
cutoffFrequency: 0
|
|
520
|
+
},
|
|
521
|
+
"low-pass": {
|
|
522
|
+
boostFactor: 0,
|
|
523
|
+
cutoffFrequency: 0
|
|
524
|
+
},
|
|
525
|
+
"lavalink-filter-plugin": {
|
|
526
|
+
echo: {
|
|
527
|
+
delay: 0,
|
|
528
|
+
decay: 0
|
|
529
|
+
},
|
|
530
|
+
reverb: {
|
|
531
|
+
delays: [],
|
|
532
|
+
gains: []
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
echo: {
|
|
536
|
+
decay: 0,
|
|
537
|
+
delay: 0,
|
|
538
|
+
echoLength: 0
|
|
539
|
+
},
|
|
540
|
+
normalization: {
|
|
541
|
+
adaptive: false,
|
|
542
|
+
maxAmplitude: 0
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
equalizer: [],
|
|
546
|
+
distortion: {
|
|
547
|
+
cosOffset: 0,
|
|
548
|
+
sinOffset: 0,
|
|
549
|
+
tanOffset: 0,
|
|
550
|
+
offset: 0,
|
|
551
|
+
scale: 1,
|
|
552
|
+
cosScale: 1,
|
|
553
|
+
sinScale: 1,
|
|
554
|
+
tanScale: 1
|
|
555
|
+
},
|
|
556
|
+
channelMix: AudioOutputData.mono
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/classes/Track.ts
|
|
560
|
+
var escapeRegExp = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
561
|
+
var Track = class {
|
|
562
|
+
/**
|
|
563
|
+
* The base64 encoded track.
|
|
564
|
+
* @type {string}
|
|
565
|
+
*/
|
|
566
|
+
encoded;
|
|
567
|
+
/**
|
|
568
|
+
* The track info.
|
|
569
|
+
* @type {TrackInfo}
|
|
570
|
+
*/
|
|
571
|
+
info;
|
|
572
|
+
/**
|
|
573
|
+
* The plugin info of the track.
|
|
574
|
+
* @type {PluginInfo}
|
|
575
|
+
*/
|
|
576
|
+
pluginInfo;
|
|
577
|
+
/**
|
|
578
|
+
* The track user data.
|
|
579
|
+
* @type {Record<string, unknown>}
|
|
580
|
+
*/
|
|
581
|
+
userData;
|
|
582
|
+
/**
|
|
583
|
+
* The requester of the track.
|
|
584
|
+
* @type {TrackRequester}
|
|
585
|
+
*/
|
|
586
|
+
requester;
|
|
587
|
+
/**
|
|
588
|
+
* The constructor for the track.
|
|
589
|
+
* @param {LavalinkTrack} track The track to construct the track from.
|
|
590
|
+
* @param {TrackRequester} requester The requester of the track.
|
|
591
|
+
* @example
|
|
592
|
+
* ```ts
|
|
593
|
+
* const track = new Track({
|
|
594
|
+
* encoded: "base64",
|
|
595
|
+
* info: {
|
|
596
|
+
* title: "Track Title",
|
|
597
|
+
* uri: "https://example.com",
|
|
598
|
+
* duration: 300000,
|
|
599
|
+
* },
|
|
600
|
+
* // the rest of the track info
|
|
601
|
+
* }, requester);
|
|
602
|
+
*
|
|
603
|
+
* console.log(track.encoded); // the track encoded in base64
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
constructor(track, requester) {
|
|
607
|
+
if (!track) throw new ResolveError("Track is not defined for construction.");
|
|
608
|
+
this.info = track.info;
|
|
609
|
+
this.encoded = track.encoded;
|
|
610
|
+
this.requester = requester ?? {};
|
|
611
|
+
this.pluginInfo = track.pluginInfo;
|
|
612
|
+
this.userData = track.userData ?? {};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
*
|
|
616
|
+
* Get the hyperlink of the track.
|
|
617
|
+
* @param {boolean} [embedable=true] Whether the hyperlink should be embedable or not.
|
|
618
|
+
* @returns {string} The hyperlink of the track.
|
|
619
|
+
* @example
|
|
620
|
+
* ```ts
|
|
621
|
+
* const track = queue.current;
|
|
622
|
+
* console.log(track.toHyperlink()); // [Track Title](https://example.com)
|
|
623
|
+
* console.log(track.toHyperlink(false)); // [Track Title](<https://example.com>)
|
|
624
|
+
* ```
|
|
625
|
+
*/
|
|
626
|
+
toHyperlink(embedable = true) {
|
|
627
|
+
if (embedable) return `[${this.info.title}](${this.info.uri})`;
|
|
628
|
+
return `[${this.info.title}](<${this.info.uri}>)`;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var UnresolvedTrack = class {
|
|
632
|
+
/**
|
|
633
|
+
* The base64 encoded track.
|
|
634
|
+
* @type {string | undefined}
|
|
635
|
+
*/
|
|
636
|
+
encoded;
|
|
637
|
+
/**
|
|
638
|
+
* The track info.
|
|
639
|
+
* @type {UnresolvedTrackInfo}
|
|
640
|
+
*/
|
|
641
|
+
info;
|
|
642
|
+
/**
|
|
643
|
+
* The plugin info of the track.
|
|
644
|
+
* @type {Partial<PluginInfo>}
|
|
645
|
+
*/
|
|
646
|
+
pluginInfo;
|
|
647
|
+
/**
|
|
648
|
+
* The track user data.
|
|
649
|
+
* @type {Record<string, unknown> | undefined}
|
|
650
|
+
*/
|
|
651
|
+
userData;
|
|
652
|
+
/**
|
|
653
|
+
* The requester of the track.
|
|
654
|
+
* @type {TrackRequester | undefined}
|
|
655
|
+
*/
|
|
656
|
+
requester;
|
|
657
|
+
/**
|
|
658
|
+
* The constructor for the track.
|
|
659
|
+
* @param {UnresolvedLavalinkTrack} track The track to construct the track from.
|
|
660
|
+
* @param {TrackRequester} requester The requester of the track.
|
|
661
|
+
* @example
|
|
662
|
+
* ```ts
|
|
663
|
+
* const track = new UnresolvedTrack({
|
|
664
|
+
* encoded: "base64",
|
|
665
|
+
* info: {
|
|
666
|
+
* title: "Track Title",
|
|
667
|
+
* },
|
|
668
|
+
* // the rest of the track info
|
|
669
|
+
* }, requester);
|
|
670
|
+
*
|
|
671
|
+
* console.log(track.encoded); // the track encoded in base64
|
|
672
|
+
* ```
|
|
673
|
+
*/
|
|
674
|
+
constructor(track, requester) {
|
|
675
|
+
this.info = track.info;
|
|
676
|
+
this.encoded = track.encoded;
|
|
677
|
+
this.requester = requester;
|
|
678
|
+
this.pluginInfo = track.pluginInfo;
|
|
679
|
+
this.userData = track.userData ?? {};
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Resolves the track to a playable track.
|
|
683
|
+
* @param {PlayerStructure} player The player to resolve the track for.
|
|
684
|
+
* @returns {Promise<Track>} The resolved track.
|
|
685
|
+
* @throws {ResolveError} If the track cannot be resolved.
|
|
686
|
+
*/
|
|
687
|
+
async resolve(player) {
|
|
688
|
+
if (!player) throw new ResolveError("Player is not defined for track resolution.");
|
|
689
|
+
if (isTrack(this)) return new Track(this, this.requester);
|
|
690
|
+
if (!isUnresolvedTrack(this)) throw new ResolveError("Track is not an unresolved track.");
|
|
691
|
+
if (!this.requester) throw new ResolveError("Requester is not defined for track resolution.");
|
|
692
|
+
if (!this.info.title && !this.encoded && !this.info.uri)
|
|
693
|
+
throw new ResolveError("Track is missing required properties for resolution.");
|
|
694
|
+
player.manager.emit("debug" /* Debug */, 3 /* Player */, `[Unresolved] -> [Track] Resolving the track: ${this.info.title}`);
|
|
695
|
+
if (this.encoded) return player.node.decode.single(this.encoded, this.requester);
|
|
696
|
+
if (this.info.uri) {
|
|
697
|
+
const track = await player.search({ query: this.info.uri, requester: this.requester }).then((result) => result.tracks[0]);
|
|
698
|
+
if (!track) throw new ResolveError("Track could not be resolved from URI.");
|
|
699
|
+
player.manager.emit("debug" /* Debug */, 3 /* Player */, `[Unresolved] -> [Track] Resolved the track from URI: ${this.info.uri}`);
|
|
700
|
+
return track;
|
|
701
|
+
}
|
|
702
|
+
const query = [this.info.title, this.info.uri].filter(Boolean).join(" by ");
|
|
703
|
+
const excluded = ["twitch" /* Twitch */, "flowery-tts" /* FloweryTTS */, "mixer" /* Mixer */, "vimeo" /* Vimeo */];
|
|
704
|
+
const engine = this.info.sourceName && !excluded.includes(this.info.sourceName) ? validateEngine(this.info.sourceName) : player.manager.options.defaultSearchEngine;
|
|
705
|
+
player.manager.emit(
|
|
706
|
+
"debug" /* Debug */,
|
|
707
|
+
3 /* Player */,
|
|
708
|
+
`[Unresolved] -> [Track] Searching for track with query: ${query} using engine: ${engine}`
|
|
709
|
+
);
|
|
710
|
+
return player.search({ query, engine, requester: this.requester }).then((result) => {
|
|
711
|
+
let track = result.tracks[0] ?? null;
|
|
712
|
+
if (this.info.author && !track)
|
|
713
|
+
track = result.tracks.find(
|
|
714
|
+
(t) => [this.info.author ?? "", `${this.info.author} - Topic`].some(
|
|
715
|
+
(name) => new RegExp(`^${escapeRegExp(name)}$`, "i").test(t.info.author)
|
|
716
|
+
) || new RegExp(`^${escapeRegExp(this.info.title)}$`, "i").test(t.info.title)
|
|
717
|
+
) ?? null;
|
|
718
|
+
if (this.info.length && !track)
|
|
719
|
+
track = result.tracks.find((t) => {
|
|
720
|
+
const length = this.info.length;
|
|
721
|
+
if (!length) return false;
|
|
722
|
+
return t.info.length >= length - 1500 && t.info.length <= length + 1500;
|
|
723
|
+
}) ?? null;
|
|
724
|
+
if (this.info.isrc && !track) track = result.tracks.find((t) => t.info.isrc === this.info.isrc) ?? null;
|
|
725
|
+
if (!track) throw new ResolveError("Track could not be resolved from search query.");
|
|
726
|
+
player.manager.emit(
|
|
727
|
+
"debug" /* Debug */,
|
|
728
|
+
3 /* Player */,
|
|
729
|
+
`[Unresolved] -> [Track] Resolved the track ${track.info.title} from search query: ${query}`
|
|
730
|
+
);
|
|
731
|
+
return track;
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/classes/queue/adapters/abstract.ts
|
|
737
|
+
var StorageAdapter = class {
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// src/util/functions/utils.ts
|
|
741
|
+
function validateManagerOptions(options) {
|
|
742
|
+
if (!Array.isArray(options.nodes) || !options.nodes.every(isNode) || !options.nodes.length)
|
|
743
|
+
throw new OptionError("The manager option 'options.nodes' must be a valid array of nodes and atleast one valid node.");
|
|
744
|
+
if (typeof options.sendPayload !== "function")
|
|
745
|
+
throw new OptionError("The manager option 'options.sendPayload' must be a vaid function.");
|
|
746
|
+
if (typeof options.queueOptions !== "undefined" && typeof options.queueOptions.maxPreviousTracks !== "number")
|
|
747
|
+
throw new OptionError("The manager option 'options.queueOptions.maxPreviousTracks' must be a number.");
|
|
748
|
+
if (typeof options.queueOptions !== "undefined" && typeof options.queueOptions.autoplayFn !== "function")
|
|
749
|
+
throw new OptionError("The manager option 'options.queueOptions.autoplayFn' must be a function.");
|
|
750
|
+
if (typeof options.queueOptions?.storage !== "undefined" && !(options.queueOptions.storage instanceof StorageAdapter))
|
|
751
|
+
throw new OptionError("The manager option 'options.queueOptions.storage' must be a valid storage manager.");
|
|
752
|
+
if (typeof options.defaultSearchEngine !== "undefined" && !ValidEngines.includes(options.defaultSearchEngine))
|
|
753
|
+
throw new OptionError("The manager option 'options.defaultSearchEngine' Must be a valid search engine.");
|
|
754
|
+
if (typeof options.client !== "undefined" && typeof options.client !== "object")
|
|
755
|
+
throw new OptionError("The manager option 'options.client' Must be a valid object.");
|
|
756
|
+
if (typeof options.client !== "undefined" && typeof options.client.id !== "undefined" && typeof options.client.id !== "string")
|
|
757
|
+
throw new OptionError("The manager option 'options.client.id' Must be a valid string.");
|
|
758
|
+
if (typeof options.client !== "undefined" && typeof options.client.id !== "undefined" && typeof options.client.username !== "string")
|
|
759
|
+
throw new OptionError("The manager option 'options.client.username' must be a valid string.");
|
|
760
|
+
}
|
|
761
|
+
function validateQuery(search) {
|
|
762
|
+
if (typeof search !== "object") throw new OptionError("The 'query' must be a valid object.");
|
|
763
|
+
if (typeof search.query !== "string") throw new OptionError("The query option 'query.query' must be a valid string.");
|
|
764
|
+
if (typeof search.engine !== "string") throw new OptionError("The query option 'query.engine' must be a valid search engine.");
|
|
765
|
+
search.engine = validateEngine(search.engine);
|
|
766
|
+
if (!ValidEngines.includes(search.engine)) throw new OptionError(`The query option 'query.engine' must be a valid search engine.`);
|
|
767
|
+
const query = search.query.trim();
|
|
768
|
+
const engineKey = Object.values(SearchEngines).find((key) => query.toLowerCase().startsWith(key));
|
|
769
|
+
if (engineKey && query.toLowerCase().startsWith(`${engineKey}:`)) {
|
|
770
|
+
const sliced = query.slice(engineKey.length + 1).trim();
|
|
771
|
+
const isUrl2 = UrlRegex.test(sliced);
|
|
772
|
+
if (isUrl2) return sliced;
|
|
773
|
+
return `${engineKey}:${sliced}`;
|
|
774
|
+
}
|
|
775
|
+
const isUrl = UrlRegex.test(query);
|
|
776
|
+
if (isUrl) return query;
|
|
777
|
+
if (search.engine === "ftts" /* FloweryTTS */) return `${search.engine}://${query}`;
|
|
778
|
+
if (search.engine !== "local" /* Local */) return `${search.engine}:${query}`;
|
|
779
|
+
return query;
|
|
780
|
+
}
|
|
781
|
+
function validatePlayerOptions(options) {
|
|
782
|
+
if (typeof options.guildId !== "string") throw new OptionError("The player option 'options.guildId' must be a string.");
|
|
783
|
+
if (typeof options.voiceId !== "string") throw new OptionError("The player option 'options.voiceId' Must be a string.");
|
|
784
|
+
if (typeof options.textId !== "undefined" && typeof options.textId !== "string")
|
|
785
|
+
throw new OptionError("The player option 'options.textId' Must be a string.");
|
|
786
|
+
if (typeof options.selfDeaf !== "undefined" && typeof options.selfDeaf !== "boolean")
|
|
787
|
+
throw new OptionError("The player option 'options.selfDeaf' Must be a boolean.");
|
|
788
|
+
if (typeof options.selfMute !== "undefined" && typeof options.selfMute !== "boolean")
|
|
789
|
+
throw new OptionError("The player option 'options.selfMute' Mute must be a boolean.");
|
|
790
|
+
if (typeof options.volume !== "undefined" && typeof options.volume !== "number")
|
|
791
|
+
throw new OptionError("The player option 'options.volume' Must be a number.");
|
|
792
|
+
}
|
|
793
|
+
function validatePlayerData(data) {
|
|
794
|
+
if (typeof data === "object" && typeof data.playerOptions === "object" && typeof data.guildId === "string" && Object.keys(data.playerOptions).length > 0) {
|
|
795
|
+
const player = this.nodeManager.manager.getPlayer(data.guildId);
|
|
796
|
+
if (!player) return;
|
|
797
|
+
if (typeof data.playerOptions.voice === "object") player.voice = data.playerOptions.voice;
|
|
798
|
+
if (typeof data.playerOptions.paused === "boolean") {
|
|
799
|
+
player.paused = data.playerOptions.paused;
|
|
800
|
+
player.playing = !data.playerOptions.paused;
|
|
801
|
+
}
|
|
802
|
+
if (typeof data.playerOptions.volume === "number") player.volume = data.playerOptions.volume;
|
|
803
|
+
if (typeof data.playerOptions.position === "number") player.position = data.playerOptions.position;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function validateNodePlugins(node, plugins) {
|
|
807
|
+
const info = node.info;
|
|
808
|
+
if (!info) throw new NodeError({ id: node.id, message: "Node is not ready yet." });
|
|
809
|
+
if (!info.plugins.length)
|
|
810
|
+
throw new NodeError({
|
|
811
|
+
id: node.id,
|
|
812
|
+
message: "No plugins found in the node."
|
|
813
|
+
});
|
|
814
|
+
const missings = plugins.filter((name) => !info.plugins.some((p) => p.name === name));
|
|
815
|
+
if (missings.length)
|
|
816
|
+
throw new NodeError({
|
|
817
|
+
id: node.id,
|
|
818
|
+
message: `The node does not support the following plugins: ${missings.join(", ")}.`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
function validateEngine(type) {
|
|
822
|
+
const source = ValidEngines.find((engine) => engine === type) ?? ValidSources.get(type);
|
|
823
|
+
if (!source) throw new OptionError(`The engine '${type}' is not a valid engine.`);
|
|
824
|
+
return source;
|
|
825
|
+
}
|
|
826
|
+
function validateTrack(player, track) {
|
|
827
|
+
if (!track) return Promise.resolve(null);
|
|
828
|
+
if (isTrack(track)) return Promise.resolve(new Track(track, track.requester));
|
|
829
|
+
if (!isUnresolvedTrack(track)) throw new ResolveError("The track is not a valid unresolved track.");
|
|
830
|
+
if (!track.resolve || typeof track.resolve !== "function") return new UnresolvedTrack(track, track.requester).resolve(player);
|
|
831
|
+
return track.resolve(player);
|
|
832
|
+
}
|
|
833
|
+
var isTrack = (track) => {
|
|
834
|
+
if (!track) return false;
|
|
835
|
+
return typeof track.encoded === "string" && typeof track.info === "object" && !("resolve" in track && typeof track.resolve === "function");
|
|
836
|
+
};
|
|
837
|
+
function isUnresolvedTrack(track) {
|
|
838
|
+
if (!track) return false;
|
|
839
|
+
return typeof track.encoded === "string" || typeof track.info === "object" && typeof track.info.title === "string" && "resolve" in track && typeof track.resolve === "function";
|
|
840
|
+
}
|
|
841
|
+
function isValid(value) {
|
|
842
|
+
return typeof value !== "undefined" && value !== null;
|
|
843
|
+
}
|
|
844
|
+
function isNode(options) {
|
|
845
|
+
return typeof options.host === "string" && typeof options.port === "number" && typeof options.password === "string" && (typeof options.id === "string" || typeof options.id === "undefined") && (typeof options.secure === "boolean" || typeof options.secure === "undefined") && (typeof options.sessionId === "string" || typeof options.sessionId === "undefined") && (typeof options.retryAmount === "number" || typeof options.retryAmount === "undefined") && (typeof options.retryDelay === "number" || typeof options.retryDelay === "undefined");
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/util/events/player.ts
|
|
849
|
+
async function onEnd() {
|
|
850
|
+
if (this.queue.current && !this.queue.history.find(
|
|
851
|
+
(x) => x.info.identifier === this.queue.current.info.identifier && x.info.title === this.queue.current.info.title
|
|
852
|
+
)) {
|
|
853
|
+
this.queue.history.unshift(this.queue.current);
|
|
854
|
+
if (this.queue.history.length > this.manager.options.queueOptions.maxPreviousTracks)
|
|
855
|
+
this.queue.history.splice(this.manager.options.queueOptions.maxPreviousTracks, this.queue.history.length);
|
|
856
|
+
await this.queue.utils.save();
|
|
857
|
+
this.manager.emit(
|
|
858
|
+
"debug" /* Debug */,
|
|
859
|
+
3 /* Player */,
|
|
860
|
+
`[Player] -> [Previous] The track: ${this.queue.current.info.title} has been added to the previous track list.`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
if (this.loop === 1 /* Track */ && this.queue.current) this.queue.unshift(this.queue.current);
|
|
864
|
+
if (this.loop === 2 /* Queue */ && this.queue.current) this.queue.add(this.queue.current);
|
|
865
|
+
if (!this.queue.current) this.queue.current = await validateTrack(this, this.queue.shift());
|
|
866
|
+
await this.queue.utils.save();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
async function queueEnd(track, payload) {
|
|
870
|
+
this.playing = false;
|
|
871
|
+
this.paused = false;
|
|
872
|
+
this.queue.current = null;
|
|
873
|
+
if (typeof this.manager.options.queueOptions.autoplayFn === "function") {
|
|
874
|
+
await this.manager.options.queueOptions.autoplayFn(this, track);
|
|
875
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, "[Queue] -> [Autoplay] Autoplay function executed.");
|
|
876
|
+
if (this.queue.size > 0) await onEnd.call(this);
|
|
877
|
+
if (this.queue.current) {
|
|
878
|
+
if (payload.type === "TrackEndEvent" /* TrackEnd */) this.manager.emit("trackEnd" /* TrackEnd */, this, track, payload);
|
|
879
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, "[Queue] -> [Autoplay] Track(s) queued from autoplay function.");
|
|
880
|
+
return this.play({ noReplace: true, paused: false });
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (track) await this.queue.utils.save();
|
|
884
|
+
if (payload.type === "TrackEndEvent" /* TrackEnd */ && payload.reason !== "stopped" /* Stopped */) await this.queue.utils.save();
|
|
885
|
+
this.manager.emit("queueEnd" /* QueueEnd */, this, this.queue);
|
|
886
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Queue] The queue has ended.");
|
|
887
|
+
}
|
|
888
|
+
async function trackStart(payload) {
|
|
889
|
+
this.paused = false;
|
|
890
|
+
this.playing = true;
|
|
891
|
+
this.manager.emit("trackStart" /* TrackStart */, this, this.queue.current, payload);
|
|
892
|
+
this.manager.emit(
|
|
893
|
+
"debug" /* Debug */,
|
|
894
|
+
3 /* Player */,
|
|
895
|
+
`[Player] -> [Start] The track: ${this.queue.current?.info.title ?? "Unknown"} has started playing.`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
async function trackEnd(payload) {
|
|
899
|
+
const current = this.queue.current;
|
|
900
|
+
if (!this.queue.size && this.loop === 3 /* Off */) return queueEnd.call(this, current, payload);
|
|
901
|
+
switch (payload.reason) {
|
|
902
|
+
case "stopped" /* Stopped */:
|
|
903
|
+
break;
|
|
904
|
+
case "replaced" /* Replaced */: {
|
|
905
|
+
this.manager.emit("trackEnd" /* TrackEnd */, this, current, payload);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
case "loadFailed" /* LoadFailed */:
|
|
909
|
+
case "cleanup" /* Cleanup */: {
|
|
910
|
+
this.playing = false;
|
|
911
|
+
await onEnd.call(this);
|
|
912
|
+
if (!this.queue.size || !this.queue.current) return queueEnd.call(this, current, payload);
|
|
913
|
+
this.manager.emit("trackEnd" /* TrackEnd */, this, current, payload);
|
|
914
|
+
this.manager.emit(
|
|
915
|
+
"debug" /* Debug */,
|
|
916
|
+
3 /* Player */,
|
|
917
|
+
`[Player] -> [End] The track: ${current?.info.title ?? "Unknown"} has ended.`
|
|
918
|
+
);
|
|
919
|
+
this.queue.current = null;
|
|
920
|
+
return this.play();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (current) await this.queue.utils.save();
|
|
924
|
+
await onEnd.call(this);
|
|
925
|
+
this.queue.current = null;
|
|
926
|
+
if (!this.queue.size) {
|
|
927
|
+
this.playing = false;
|
|
928
|
+
return queueEnd.call(this, current, payload);
|
|
929
|
+
}
|
|
930
|
+
this.manager.emit("trackEnd" /* TrackEnd */, this, current, payload);
|
|
931
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [End] The track: ${current?.info.title ?? "Uhknown"} has ended.`);
|
|
932
|
+
return this.play();
|
|
933
|
+
}
|
|
934
|
+
async function trackStuck(payload) {
|
|
935
|
+
this.manager.emit("trackStuck" /* TrackStuck */, this, this.queue.current, payload);
|
|
936
|
+
this.manager.emit(
|
|
937
|
+
"debug" /* Debug */,
|
|
938
|
+
3 /* Player */,
|
|
939
|
+
`[Player] -> [Stuck] The track: ${this.queue.current?.info.title ?? "Unknown"} has stuck.`
|
|
940
|
+
);
|
|
941
|
+
if (!this.queue.size && this.loop === 3 /* Off */) {
|
|
942
|
+
try {
|
|
943
|
+
await this.node.updatePlayer({
|
|
944
|
+
guildId: this.guildId,
|
|
945
|
+
playerOptions: { track: { encoded: null } }
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
} catch {
|
|
949
|
+
return queueEnd.call(this, this.queue.current, payload);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
await onEnd.call(this);
|
|
953
|
+
if (!this.queue.current) return queueEnd.call(this, this.queue.current, payload);
|
|
954
|
+
}
|
|
955
|
+
async function trackError(payload) {
|
|
956
|
+
this.manager.emit("trackError" /* TrackError */, this, this.queue.current, payload);
|
|
957
|
+
this.manager.emit(
|
|
958
|
+
"debug" /* Debug */,
|
|
959
|
+
3 /* Player */,
|
|
960
|
+
`[Player] -> [Error] The track: ${this.queue.current?.info.title ?? "Unknown"} has error.`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
async function playerUpdate(payload) {
|
|
964
|
+
const player = this.nodeManager.manager.getPlayer(payload.guildId);
|
|
965
|
+
if (!player) return;
|
|
966
|
+
const oldPlayer = player.toJSON();
|
|
967
|
+
player.ping = payload.state.ping;
|
|
968
|
+
player.connected = payload.state.connected;
|
|
969
|
+
player.createdTimestamp = payload.state.time;
|
|
970
|
+
player.position = payload.state.position;
|
|
971
|
+
this.nodeManager.manager.emit("playerUpdate" /* PlayerUpdate */, player, oldPlayer, payload);
|
|
972
|
+
this.nodeManager.manager.emit(
|
|
973
|
+
"debug" /* Debug */,
|
|
974
|
+
2 /* Node */,
|
|
975
|
+
`[Player] -> [Update] Player updated: ${player.guildId} | Payload: ${JSON.stringify(payload)}`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
async function lyricsFound(track, payload) {
|
|
979
|
+
this.manager.emit("lyricsFound" /* LyricsFound */, this, track, payload);
|
|
980
|
+
this.manager.emit(
|
|
981
|
+
"debug" /* Debug */,
|
|
982
|
+
3 /* Player */,
|
|
983
|
+
`[Player] -> [Lyrics] The lyrics have been found: ${this.guildId} | Payload: ${JSON.stringify(payload)}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
async function lyricsLine(track, payload) {
|
|
987
|
+
this.manager.emit("lyricsLine" /* LyricsLine */, this, track, payload);
|
|
988
|
+
this.manager.emit(
|
|
989
|
+
"debug" /* Debug */,
|
|
990
|
+
3 /* Player */,
|
|
991
|
+
`[Player] -> [Lyrics] The lyrics line has been found: ${this.guildId} | Payload: ${JSON.stringify(payload)}`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
async function lyricsNotFound(track, payload) {
|
|
995
|
+
this.manager.emit("lyricsNotFound" /* LyricsNotFound */, this, track, payload);
|
|
996
|
+
this.manager.emit(
|
|
997
|
+
"debug" /* Debug */,
|
|
998
|
+
3 /* Player */,
|
|
999
|
+
`[Player] -> [Lyrics] The lyrics were not found: ${this.guildId} | Payload: ${JSON.stringify(payload)}`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
async function socketClosed(payload) {
|
|
1003
|
+
this.manager.emit("socketClosed" /* WebSocketClosed */, this, payload);
|
|
1004
|
+
this.manager.emit(
|
|
1005
|
+
"debug" /* Debug */,
|
|
1006
|
+
3 /* Player */,
|
|
1007
|
+
`[Player] -> [Socket] The socket has closed: ${this.guildId} | Payload: ${JSON.stringify(payload)}`
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/util/events/websocket.ts
|
|
1012
|
+
function onOpen(res) {
|
|
1013
|
+
const isResume = res.headers["session-resumed"] === "true";
|
|
1014
|
+
const apiVersion = res.headers["lavalink-api-version"] ?? "unknown";
|
|
1015
|
+
this.retryAmount = this.options.retryAmount;
|
|
1016
|
+
this.nodeManager.manager.emit(
|
|
1017
|
+
"debug" /* Debug */,
|
|
1018
|
+
2 /* Node */,
|
|
1019
|
+
`[Socket] -> [${this.id}]: Connection handshake complete with ${this.address}. | API Version: ${apiVersion} | Resumed: ${isResume}`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
function onClose(code, reason) {
|
|
1023
|
+
this.nodeManager.manager.emit(
|
|
1024
|
+
"debug" /* Debug */,
|
|
1025
|
+
2 /* Node */,
|
|
1026
|
+
`[Socket] -> [${this.id}]: Connection closed with ${this.address}. | Code: ${code} | Reason: ${reason}`
|
|
1027
|
+
);
|
|
1028
|
+
this.nodeManager.manager.emit("nodeDisconnect" /* NodeDisconnect */, this);
|
|
1029
|
+
if (code !== 1e3 /* NormalClosure */ || reason !== "Node-Destroy" /* Destroy */) {
|
|
1030
|
+
if (this.nodeManager.nodes.has(this.id)) this.reconnect();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function onError(error) {
|
|
1034
|
+
if (!error) return;
|
|
1035
|
+
if (this.reconnectTimeout) {
|
|
1036
|
+
clearInterval(this.reconnectTimeout);
|
|
1037
|
+
this.reconnectTimeout = null;
|
|
1038
|
+
}
|
|
1039
|
+
this.nodeManager.manager.emit(
|
|
1040
|
+
"debug" /* Debug */,
|
|
1041
|
+
2 /* Node */,
|
|
1042
|
+
`[Socket] -> [${this.id}]: Connection error with ${this.address}. | Error: ${error.message}`
|
|
1043
|
+
);
|
|
1044
|
+
this.nodeManager.manager.emit("nodeError" /* NodeError */, this, error);
|
|
1045
|
+
}
|
|
1046
|
+
async function onMessage(message) {
|
|
1047
|
+
if (Array.isArray(message)) message = Buffer.concat(message);
|
|
1048
|
+
else if (message instanceof ArrayBuffer) message = Buffer.from(message);
|
|
1049
|
+
try {
|
|
1050
|
+
const payload = JSON.parse(message.toString());
|
|
1051
|
+
if (!payload.op) return;
|
|
1052
|
+
this.nodeManager.manager.emit("nodeRaw" /* NodeRaw */, this, payload);
|
|
1053
|
+
switch (payload.op) {
|
|
1054
|
+
case "stats" /* Stats */:
|
|
1055
|
+
{
|
|
1056
|
+
this.stats = payload;
|
|
1057
|
+
this.nodeManager.manager.emit(
|
|
1058
|
+
"debug" /* Debug */,
|
|
1059
|
+
2 /* Node */,
|
|
1060
|
+
`[Socket] <- [${this.id}]: Received stats. | System load: ${this.penalties}`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
break;
|
|
1064
|
+
case "ready" /* Ready */:
|
|
1065
|
+
{
|
|
1066
|
+
if (!payload.sessionId) {
|
|
1067
|
+
this.nodeManager.manager.emit(
|
|
1068
|
+
"debug" /* Debug */,
|
|
1069
|
+
2 /* Node */,
|
|
1070
|
+
`[Socket] -> [${this.id}]: Session id was not provided. Breaking up the connection...`
|
|
1071
|
+
);
|
|
1072
|
+
this.nodeManager.manager.emit("nodeDestroy" /* NodeDestroy */, this, {
|
|
1073
|
+
code: 1006 /* AbnormalClosure */,
|
|
1074
|
+
reason: "Missing-Session" /* MissingSession */
|
|
1075
|
+
});
|
|
1076
|
+
return this.disconnect();
|
|
1077
|
+
}
|
|
1078
|
+
this.state = 2 /* Connected */;
|
|
1079
|
+
this.sessionId = payload.sessionId;
|
|
1080
|
+
this.session.resuming = payload.resumed;
|
|
1081
|
+
if (payload.resumed) {
|
|
1082
|
+
const players = await this.rest.request({
|
|
1083
|
+
endpoint: `/sessions/${payload.sessionId}/players`
|
|
1084
|
+
}) ?? [];
|
|
1085
|
+
const timeout = this.nodeManager.manager.options.nodeOptions.resumeTimeout;
|
|
1086
|
+
this.nodeManager.manager.emit("nodeResumed" /* NodeResumed */, this, players, payload);
|
|
1087
|
+
this.nodeManager.manager.emit(
|
|
1088
|
+
"debug" /* Debug */,
|
|
1089
|
+
2 /* Node */,
|
|
1090
|
+
`[Socket] <- [${this.id}]: Resumed session. | Session id: ${payload.sessionId} | Players: ${players.length} | Resumed: ${payload.resumed} | Timeout: ${timeout}ms`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
this.info = await this.rest.request({ endpoint: "/info" });
|
|
1094
|
+
this.nodeManager.manager.emit(
|
|
1095
|
+
"debug" /* Debug */,
|
|
1096
|
+
2 /* Node */,
|
|
1097
|
+
`[Socket] <- [${this.id}]: Received ready event. | Session id: ${payload.sessionId} | Resumed: ${payload.resumed}`
|
|
1098
|
+
);
|
|
1099
|
+
this.nodeManager.manager.emit("nodeReady" /* NodeReady */, this, this.retryAmount, payload);
|
|
1100
|
+
const isResumable = this.nodeManager.manager.options.nodeOptions.resumable;
|
|
1101
|
+
if (isResumable) {
|
|
1102
|
+
const timeout = this.nodeManager.manager.options.nodeOptions.resumeTimeout;
|
|
1103
|
+
this.nodeManager.manager.emit(
|
|
1104
|
+
"debug" /* Debug */,
|
|
1105
|
+
2 /* Node */,
|
|
1106
|
+
`[Socket] -> [${this.id}]: Resuming session... | Timeout: ${timeout}ms`
|
|
1107
|
+
);
|
|
1108
|
+
await this.updateSession(isResumable, timeout);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
break;
|
|
1112
|
+
case "event" /* Event */: {
|
|
1113
|
+
const player = this.nodeManager.manager.getPlayer(payload.guildId);
|
|
1114
|
+
if (!player) return;
|
|
1115
|
+
switch (payload.type) {
|
|
1116
|
+
//
|
|
1117
|
+
// Events related to tracks.
|
|
1118
|
+
//
|
|
1119
|
+
case "TrackEndEvent" /* TrackEnd */:
|
|
1120
|
+
await trackEnd.call(player, payload);
|
|
1121
|
+
break;
|
|
1122
|
+
case "TrackStartEvent" /* TrackStart */:
|
|
1123
|
+
await trackStart.call(player, payload);
|
|
1124
|
+
break;
|
|
1125
|
+
case "TrackExceptionEvent" /* TrackException */:
|
|
1126
|
+
await trackError.call(player, payload);
|
|
1127
|
+
break;
|
|
1128
|
+
case "TrackStuckEvent" /* TrackStuck */:
|
|
1129
|
+
await trackStuck.call(player, payload);
|
|
1130
|
+
break;
|
|
1131
|
+
//
|
|
1132
|
+
// Events related to lyrics.
|
|
1133
|
+
//
|
|
1134
|
+
case "LyricsFoundEvent" /* LyricsFound */:
|
|
1135
|
+
await lyricsFound.call(player, player.queue.current, payload);
|
|
1136
|
+
break;
|
|
1137
|
+
case "LyricsNotFoundEvent" /* LyricsNotFound */:
|
|
1138
|
+
await lyricsNotFound.call(player, player.queue.current, payload);
|
|
1139
|
+
break;
|
|
1140
|
+
case "LyricsLineEvent" /* LyricsLine */:
|
|
1141
|
+
await lyricsLine.call(player, player.queue.current, payload);
|
|
1142
|
+
break;
|
|
1143
|
+
//
|
|
1144
|
+
// Events related to the websocket.
|
|
1145
|
+
//
|
|
1146
|
+
case "WebSocketClosedEvent" /* WebsocketClosed */:
|
|
1147
|
+
await socketClosed.call(player, payload);
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
case "playerUpdate" /* PlayerUpdate */: {
|
|
1153
|
+
await playerUpdate.call(this, payload);
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
this.nodeManager.manager.emit(
|
|
1158
|
+
"debug" /* Debug */,
|
|
1159
|
+
2 /* Node */,
|
|
1160
|
+
`[Socket] -> [${this.id}]: Received payload: ${JSON.stringify(payload)}`
|
|
1161
|
+
);
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
this.nodeManager.manager.emit("nodeError" /* NodeError */, this, error);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/types/Rest.ts
|
|
1168
|
+
var HttpMethods = /* @__PURE__ */ ((HttpMethods2) => {
|
|
1169
|
+
HttpMethods2["Get"] = "GET";
|
|
1170
|
+
HttpMethods2["Post"] = "POST";
|
|
1171
|
+
HttpMethods2["Put"] = "PUT";
|
|
1172
|
+
HttpMethods2["Patch"] = "PATCH";
|
|
1173
|
+
HttpMethods2["Delete"] = "DELETE";
|
|
1174
|
+
HttpMethods2["Head"] = "HEAD";
|
|
1175
|
+
return HttpMethods2;
|
|
1176
|
+
})(HttpMethods || {});
|
|
1177
|
+
var HttpStatusCodes = /* @__PURE__ */ ((HttpStatusCodes2) => {
|
|
1178
|
+
HttpStatusCodes2[HttpStatusCodes2["OK"] = 200] = "OK";
|
|
1179
|
+
HttpStatusCodes2[HttpStatusCodes2["Created"] = 201] = "Created";
|
|
1180
|
+
HttpStatusCodes2[HttpStatusCodes2["Accepted"] = 202] = "Accepted";
|
|
1181
|
+
HttpStatusCodes2[HttpStatusCodes2["NoContent"] = 204] = "NoContent";
|
|
1182
|
+
HttpStatusCodes2[HttpStatusCodes2["MovedPermanently"] = 301] = "MovedPermanently";
|
|
1183
|
+
HttpStatusCodes2[HttpStatusCodes2["Found"] = 302] = "Found";
|
|
1184
|
+
HttpStatusCodes2[HttpStatusCodes2["NotModified"] = 304] = "NotModified";
|
|
1185
|
+
HttpStatusCodes2[HttpStatusCodes2["BadRequest"] = 400] = "BadRequest";
|
|
1186
|
+
HttpStatusCodes2[HttpStatusCodes2["Unauthorized"] = 401] = "Unauthorized";
|
|
1187
|
+
HttpStatusCodes2[HttpStatusCodes2["Forbidden"] = 403] = "Forbidden";
|
|
1188
|
+
HttpStatusCodes2[HttpStatusCodes2["NotFound"] = 404] = "NotFound";
|
|
1189
|
+
HttpStatusCodes2[HttpStatusCodes2["MethodNotAllowed"] = 405] = "MethodNotAllowed";
|
|
1190
|
+
HttpStatusCodes2[HttpStatusCodes2["RequestTimeout"] = 408] = "RequestTimeout";
|
|
1191
|
+
HttpStatusCodes2[HttpStatusCodes2["Conflict"] = 409] = "Conflict";
|
|
1192
|
+
HttpStatusCodes2[HttpStatusCodes2["Gone"] = 410] = "Gone";
|
|
1193
|
+
HttpStatusCodes2[HttpStatusCodes2["TooManyRequests"] = 429] = "TooManyRequests";
|
|
1194
|
+
HttpStatusCodes2[HttpStatusCodes2["InternalServerError"] = 500] = "InternalServerError";
|
|
1195
|
+
HttpStatusCodes2[HttpStatusCodes2["NotImplemented"] = 501] = "NotImplemented";
|
|
1196
|
+
HttpStatusCodes2[HttpStatusCodes2["BadGateway"] = 502] = "BadGateway";
|
|
1197
|
+
HttpStatusCodes2[HttpStatusCodes2["ServiceUnavailable"] = 503] = "ServiceUnavailable";
|
|
1198
|
+
HttpStatusCodes2[HttpStatusCodes2["GatewayTimeout"] = 504] = "GatewayTimeout";
|
|
1199
|
+
return HttpStatusCodes2;
|
|
1200
|
+
})(HttpStatusCodes || {});
|
|
1201
|
+
|
|
1202
|
+
// src/classes/node/Node.ts
|
|
1203
|
+
var import_ws = require("ws");
|
|
1204
|
+
|
|
1205
|
+
// src/classes/node/Lyrics.ts
|
|
1206
|
+
var LyricsManager = class {
|
|
1207
|
+
/**
|
|
1208
|
+
* The node instance.
|
|
1209
|
+
* @type {NodeStructure}
|
|
1210
|
+
* @readonly
|
|
1211
|
+
*/
|
|
1212
|
+
node;
|
|
1213
|
+
/**
|
|
1214
|
+
* Create a new LyricsManager instance.
|
|
1215
|
+
* @param {NodeStructure} node The node instance.
|
|
1216
|
+
* @example
|
|
1217
|
+
* ```ts
|
|
1218
|
+
* const node = manager.nodeManager.get("nodeId");
|
|
1219
|
+
* const lyricsManager = new LyricsManager(node);
|
|
1220
|
+
* ```
|
|
1221
|
+
*/
|
|
1222
|
+
constructor(node) {
|
|
1223
|
+
this.node = node;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
*
|
|
1227
|
+
* Get the current lyrics for the current track.
|
|
1228
|
+
* @param {boolean} skipSource Whether to skip the track source or not.
|
|
1229
|
+
* @returns {Promise<LyricsResult | null>} The lyrics result or null if not found.
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```ts
|
|
1232
|
+
* const player = manager.getPlayer("guildId");
|
|
1233
|
+
* const lyrics = await player.lyricsManager.current();
|
|
1234
|
+
* ```
|
|
1235
|
+
*/
|
|
1236
|
+
async current(guildId, skipSource = false) {
|
|
1237
|
+
if (!this.node.sessionId) return null;
|
|
1238
|
+
validateNodePlugins(this.node, ["lavalyrics-plugin" /* LavaLyrics */, "java-lyrics-plugin" /* JavaLyrics */, "lavasrc-plugin" /* LavaSrc */]);
|
|
1239
|
+
return this.node.rest.request({
|
|
1240
|
+
endpoint: `/sessions/${this.node.sessionId}/players/${guildId}/track/lyrics`,
|
|
1241
|
+
params: {
|
|
1242
|
+
skipTrackSource: `${skipSource}`
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
*
|
|
1248
|
+
* Get the lyrics for a specific track.
|
|
1249
|
+
* @param {Track} track The track to get the lyrics for.
|
|
1250
|
+
* @param {boolean} skipSource Whether to skip the track source or not.
|
|
1251
|
+
* @returns {Promise<LyricsResult | null>} The lyrics result or null if not found.
|
|
1252
|
+
* @example
|
|
1253
|
+
* ```ts
|
|
1254
|
+
* const node = manager.nodeManager.get("nodeId");
|
|
1255
|
+
* const lyrics = await node.lyricsManager.get(track);
|
|
1256
|
+
* ```
|
|
1257
|
+
*/
|
|
1258
|
+
async get(track, skipSource = false) {
|
|
1259
|
+
if (!this.node.sessionId) return null;
|
|
1260
|
+
validateNodePlugins(this.node, ["lavalyrics-plugin" /* LavaLyrics */, "java-lyrics-plugin" /* JavaLyrics */, "lavasrc-plugin" /* LavaSrc */]);
|
|
1261
|
+
return this.node.rest.request({
|
|
1262
|
+
endpoint: "/lyrics",
|
|
1263
|
+
params: {
|
|
1264
|
+
track: track.encoded,
|
|
1265
|
+
skipTrackSource: `${skipSource}`
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
*
|
|
1271
|
+
* Subscribe to the lyrics for a specific guild.
|
|
1272
|
+
* @param {string} guildId The guild id to subscribe to.
|
|
1273
|
+
* @param {boolean} skipSource Whether to skip the track source or not.
|
|
1274
|
+
* @returns {Promise<void>} Let's start the sing session!
|
|
1275
|
+
* @example
|
|
1276
|
+
* ```ts
|
|
1277
|
+
* const node = manager.nodeManager.get("nodeId");
|
|
1278
|
+
* await node.lyricsManager.subscribe("guildId");
|
|
1279
|
+
* ```
|
|
1280
|
+
*/
|
|
1281
|
+
async subscribe(guildId, skipSource = false) {
|
|
1282
|
+
if (!this.node.sessionId) return;
|
|
1283
|
+
validateNodePlugins(this.node, ["lavalyrics-plugin" /* LavaLyrics */, "java-lyrics-plugin" /* JavaLyrics */, "lavasrc-plugin" /* LavaSrc */]);
|
|
1284
|
+
await this.node.rest.request({
|
|
1285
|
+
endpoint: `/sessions/${this.node.sessionId}/players/${guildId}/lyrics/subscribe`,
|
|
1286
|
+
method: "POST" /* Post */,
|
|
1287
|
+
params: {
|
|
1288
|
+
skipTrackSource: `${skipSource}`
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
*
|
|
1294
|
+
* Unsubscribe from the lyrics for a specific guild.
|
|
1295
|
+
* @param {string} guildId The guild id to unsubscribe from.
|
|
1296
|
+
* @returns {Promise<void>} Let's stop the sing session!
|
|
1297
|
+
* @example
|
|
1298
|
+
* ```ts
|
|
1299
|
+
* const node = manager.nodeManager.get("nodeId");
|
|
1300
|
+
* await node.lyricsManager.unsubscribe("guildId");
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
async unsubscribe(guildId) {
|
|
1304
|
+
if (!this.node.sessionId) return;
|
|
1305
|
+
validateNodePlugins(this.node, ["lavalyrics-plugin" /* LavaLyrics */, "java-lyrics-plugin" /* JavaLyrics */, "lavasrc-plugin" /* LavaSrc */]);
|
|
1306
|
+
await this.node.rest.request({
|
|
1307
|
+
endpoint: `/sessions/${this.node.sessionId}/players/${guildId}/lyrics/subscribe`,
|
|
1308
|
+
method: "DELETE" /* Delete */
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1313
|
+
// src/util/collection.ts
|
|
1314
|
+
var Collection = class extends Map {
|
|
1315
|
+
/**
|
|
1316
|
+
* Removes elements from the collection based on a filter function.
|
|
1317
|
+
* @param fn The filter function that determines which elements to remove.
|
|
1318
|
+
* @param thisArg The value to use as `this` when executing the filter function.
|
|
1319
|
+
* @returns The number of elements removed from the collection.
|
|
1320
|
+
* @example
|
|
1321
|
+
* const collection = new Collection<number, string>();
|
|
1322
|
+
* collection.set(1, 'one');
|
|
1323
|
+
* collection.set(2, 'two');
|
|
1324
|
+
* collection.set(3, 'three');
|
|
1325
|
+
* const removedCount = collection.sweep((value, key) => key % 2 === 0);
|
|
1326
|
+
* console.log(removedCount); // Output: 1
|
|
1327
|
+
* console.log(collection.size); // Output: 2
|
|
1328
|
+
*/
|
|
1329
|
+
sweep(fn) {
|
|
1330
|
+
if (typeof fn !== "function") throw new TypeError("The filter function must be a function.");
|
|
1331
|
+
const previous = this.size;
|
|
1332
|
+
for (const [key, val] of this) {
|
|
1333
|
+
if (fn(val, key, this)) this.delete(key);
|
|
1334
|
+
}
|
|
1335
|
+
return previous - this.size;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Creates a new array with the results of calling a provided function on every element in the collection.
|
|
1339
|
+
* @param fn The function that produces an element of the new array.
|
|
1340
|
+
* @param thisArg The value to use as `this` when executing the map function.
|
|
1341
|
+
* @returns A new array with the results of calling the provided function on every element in the collection.
|
|
1342
|
+
* @example
|
|
1343
|
+
* const collection = new Collection<number, string>();
|
|
1344
|
+
* collection.set(1, 'one');
|
|
1345
|
+
* collection.set(2, 'two');
|
|
1346
|
+
* collection.set(3, 'three');
|
|
1347
|
+
* const mappedArray = collection.map((value, key) => `${key}: ${value}`);
|
|
1348
|
+
* console.log(mappedArray); // Output: ['1: one', '2: two', '3: three']
|
|
1349
|
+
*/
|
|
1350
|
+
map(fn) {
|
|
1351
|
+
if (typeof fn !== "function") throw new TypeError("The filter function must be a function.");
|
|
1352
|
+
const result = [];
|
|
1353
|
+
for (const [key, value] of this.entries()) {
|
|
1354
|
+
result.push(fn(value, key, this));
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Creates a new array with all elements that pass the test implemented by the provided function.
|
|
1360
|
+
* @param fn The function to test each element of the collection.
|
|
1361
|
+
* @param thisArg The value to use as `this` when executing the filter function.
|
|
1362
|
+
* @returns A new array with the elements that pass the test.
|
|
1363
|
+
* @example
|
|
1364
|
+
* const collection = new Collection<number, string>();
|
|
1365
|
+
* collection.set(1, 'one');
|
|
1366
|
+
* collection.set(2, 'two');
|
|
1367
|
+
* collection.set(3, 'three');
|
|
1368
|
+
* const filteredArray = collection.filter((value, key) => key % 2 === 0);
|
|
1369
|
+
* console.log(filteredArray); // Output: ['two']
|
|
1370
|
+
*/
|
|
1371
|
+
filter(fn) {
|
|
1372
|
+
if (typeof fn !== "function") throw new TypeError("The filter function must be a function.");
|
|
1373
|
+
const result = [];
|
|
1374
|
+
for (const [key, value] of this.entries()) {
|
|
1375
|
+
if (fn(value, key, this)) result.push(value);
|
|
1376
|
+
}
|
|
1377
|
+
return result;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Returns the value of the first element in the collection that satisfies the provided testing function.
|
|
1381
|
+
* @param fn The function to test each element of the collection.
|
|
1382
|
+
* @returns The value of the first element that passes the test. `undefined` if no element passes the test.
|
|
1383
|
+
* @example
|
|
1384
|
+
* const collection = new Collection<number, number>();
|
|
1385
|
+
* collection.set(1, 1);
|
|
1386
|
+
* collection.set(2, 2);
|
|
1387
|
+
* collection.set(3, 3);
|
|
1388
|
+
* const firstEvenValue = collection.find(value => value % 2 === 0);
|
|
1389
|
+
* console.log(firstEvenValue); // Output: 2
|
|
1390
|
+
*/
|
|
1391
|
+
find(fn) {
|
|
1392
|
+
if (typeof fn !== "function") throw new TypeError("The filter function must be a function.");
|
|
1393
|
+
for (const [key, value] of this.entries()) {
|
|
1394
|
+
if (fn(value, key, this)) {
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return void 0;
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// src/classes/node/Manager.ts
|
|
1403
|
+
var NodeManager = class {
|
|
1404
|
+
/**
|
|
1405
|
+
* The manager for the node.
|
|
1406
|
+
* @type {Hoshimi}
|
|
1407
|
+
* @readonly
|
|
1408
|
+
*/
|
|
1409
|
+
manager;
|
|
1410
|
+
/**
|
|
1411
|
+
* The nodes for the manager.
|
|
1412
|
+
* @type {Collection<string, Node>}
|
|
1413
|
+
* @readonly
|
|
1414
|
+
*/
|
|
1415
|
+
nodes = new Collection();
|
|
1416
|
+
/**
|
|
1417
|
+
*
|
|
1418
|
+
* The constructor for the node manager.
|
|
1419
|
+
* @param {Hoshimi} manager The manager for the node.
|
|
1420
|
+
* @example
|
|
1421
|
+
* ```ts
|
|
1422
|
+
* const manager = new Hoshimi();
|
|
1423
|
+
* const nodeManager = new NodeManager(manager);
|
|
1424
|
+
*
|
|
1425
|
+
* console.log(nodeManager.nodes.size); // 0
|
|
1426
|
+
* ```
|
|
1427
|
+
*/
|
|
1428
|
+
constructor(manager) {
|
|
1429
|
+
this.manager = manager;
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
*
|
|
1433
|
+
* Delete the node.
|
|
1434
|
+
* @param {string} id The id of the node to delete.
|
|
1435
|
+
* @returns {boolean} If the node was deleted.
|
|
1436
|
+
* @example
|
|
1437
|
+
* ```ts
|
|
1438
|
+
* const node = manager.nodeManager.get("node1");
|
|
1439
|
+
* if (node) manager.nodeManager.delete(node.id); // true if the node was deleted
|
|
1440
|
+
* ```
|
|
1441
|
+
*/
|
|
1442
|
+
delete(id) {
|
|
1443
|
+
return this.nodes.delete(id);
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
*
|
|
1447
|
+
* Get the node by id.
|
|
1448
|
+
* @param {string} id The id of the node.
|
|
1449
|
+
* @returns {NodeStructure | undefined} The node or undefined if not found.
|
|
1450
|
+
* @example
|
|
1451
|
+
* ```ts
|
|
1452
|
+
* const node = manager.nodeManager.get("node1");
|
|
1453
|
+
* if (node) console.log(node.id); // node1
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
get(id) {
|
|
1457
|
+
return this.nodes.get(id);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
*
|
|
1461
|
+
* Get the least used node.
|
|
1462
|
+
* @returns {NodeStructure} The least used node.
|
|
1463
|
+
* @example
|
|
1464
|
+
* ```ts
|
|
1465
|
+
* const node = manager.nodeManager.getLeastUsed();
|
|
1466
|
+
* if (node) {
|
|
1467
|
+
* console.log(node.id); // node1
|
|
1468
|
+
* console.log(node.penalties); // the penalties of the node
|
|
1469
|
+
* console.log(node.state); // the state of the node
|
|
1470
|
+
* }
|
|
1471
|
+
* ```
|
|
1472
|
+
*/
|
|
1473
|
+
getLeastUsed(sortType = "players" /* Players */) {
|
|
1474
|
+
const nodes = this.nodes.filter((node2) => node2.state === 2 /* Connected */);
|
|
1475
|
+
if (!nodes.length) throw new Error("No connected nodes available.");
|
|
1476
|
+
let node;
|
|
1477
|
+
switch (sortType) {
|
|
1478
|
+
case "players" /* Players */: {
|
|
1479
|
+
const sorted = nodes.sort((a, b) => (a.stats?.players ?? 0) - (b.stats?.players ?? 0));
|
|
1480
|
+
node = sorted[0];
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
case "playingPlayers" /* PlayingPlayers */: {
|
|
1484
|
+
const sorted = nodes.sort((a, b) => (a.stats?.playingPlayers ?? 0) - (b.stats?.playingPlayers ?? 0));
|
|
1485
|
+
node = sorted[0];
|
|
1486
|
+
break;
|
|
1487
|
+
}
|
|
1488
|
+
case "systemLoad" /* SystemLoad */: {
|
|
1489
|
+
const sorted = nodes.sort((a, b) => (a.stats?.cpu.systemLoad ?? 0) - (b.stats?.cpu.systemLoad ?? 0));
|
|
1490
|
+
node = sorted[0];
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case "lavalinkLoad" /* LavalinkLoad */: {
|
|
1494
|
+
const sorted = nodes.sort((a, b) => (a.stats?.cpu.lavalinkLoad ?? 0) - (b.stats?.cpu.lavalinkLoad ?? 0));
|
|
1495
|
+
node = sorted[0];
|
|
1496
|
+
break;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (!node) node = nodes.reduce((a, b) => a.penalties < b.penalties ? a : b);
|
|
1500
|
+
return node;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
*
|
|
1504
|
+
* Create a new node.
|
|
1505
|
+
* @param {NodeOptions} options The options for the node.
|
|
1506
|
+
* @returns {NodeStructure} The created node.
|
|
1507
|
+
* @example
|
|
1508
|
+
* ```ts
|
|
1509
|
+
* const node = manager.nodeManager.create({
|
|
1510
|
+
* host: "localhost",
|
|
1511
|
+
* port: 2333,
|
|
1512
|
+
* password: "password",
|
|
1513
|
+
* secure: false,
|
|
1514
|
+
* });
|
|
1515
|
+
*
|
|
1516
|
+
* console.log(node.id); // localhost:2333
|
|
1517
|
+
*/
|
|
1518
|
+
create(options) {
|
|
1519
|
+
options.id ??= `${options.host}:${options.port}`;
|
|
1520
|
+
const oldNode = this.nodes.get(options.id);
|
|
1521
|
+
if (oldNode) return oldNode;
|
|
1522
|
+
const node = Structures.Node(this, options);
|
|
1523
|
+
this.nodes.set(node.id, node);
|
|
1524
|
+
this.manager.emit("nodeCreate" /* NodeCreate */, node);
|
|
1525
|
+
return node;
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
*
|
|
1529
|
+
* Reconnect the nodes.
|
|
1530
|
+
* @returns {void}
|
|
1531
|
+
* @example
|
|
1532
|
+
* ```ts
|
|
1533
|
+
* const node = manager.nodeManager.get("node1");
|
|
1534
|
+
* if (node) node.reconnect();
|
|
1535
|
+
* ```
|
|
1536
|
+
*/
|
|
1537
|
+
reconnect() {
|
|
1538
|
+
if (!this.nodes.size) return;
|
|
1539
|
+
for (const node of this.nodes.filter((node2) => node2.state !== 2 /* Connected */)) {
|
|
1540
|
+
node.reconnect();
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Disconnect the nodes.
|
|
1545
|
+
* @returns {void}
|
|
1546
|
+
* @example
|
|
1547
|
+
* ```ts
|
|
1548
|
+
* const node = manager.nodeManager.get("node1");
|
|
1549
|
+
* if (node) node.disconnect();
|
|
1550
|
+
* ```
|
|
1551
|
+
*/
|
|
1552
|
+
disconnect() {
|
|
1553
|
+
if (!this.nodes.size) return;
|
|
1554
|
+
for (const node of this.nodes.filter((node2) => node2.state !== 3 /* Disconnected */)) {
|
|
1555
|
+
node.disconnect();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Connect the nodes.
|
|
1560
|
+
* @returns {void}
|
|
1561
|
+
* @example
|
|
1562
|
+
* ```ts
|
|
1563
|
+
* const node = manager.nodeManager.get("node1");
|
|
1564
|
+
* if (node) node.connect();
|
|
1565
|
+
* ```
|
|
1566
|
+
*/
|
|
1567
|
+
connect() {
|
|
1568
|
+
if (!this.nodes.size) return;
|
|
1569
|
+
for (const node of this.nodes.filter((node2) => node2.state !== 2 /* Connected */)) {
|
|
1570
|
+
node.connect();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Destroy the nodes.
|
|
1575
|
+
* @returns {void}
|
|
1576
|
+
* @example
|
|
1577
|
+
* ```ts
|
|
1578
|
+
* const node = manager.nodeManager.get("node1");
|
|
1579
|
+
* if (node) node.destroy();
|
|
1580
|
+
* ```
|
|
1581
|
+
*/
|
|
1582
|
+
destroy() {
|
|
1583
|
+
if (!this.nodes.size) return;
|
|
1584
|
+
for (const node of this.nodes.values()) {
|
|
1585
|
+
node.destroy();
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
// src/classes/node/Rest.ts
|
|
1591
|
+
var RestError = class extends Error {
|
|
1592
|
+
/**
|
|
1593
|
+
* The timestamp of the response.
|
|
1594
|
+
* @type {number}
|
|
1595
|
+
*/
|
|
1596
|
+
timestamp;
|
|
1597
|
+
/**
|
|
1598
|
+
* The status of the response.
|
|
1599
|
+
* @type {number}
|
|
1600
|
+
*/
|
|
1601
|
+
status;
|
|
1602
|
+
/**
|
|
1603
|
+
* The error of the response.
|
|
1604
|
+
* @type {string}
|
|
1605
|
+
*/
|
|
1606
|
+
error;
|
|
1607
|
+
/**
|
|
1608
|
+
* The message of the response.
|
|
1609
|
+
* @type {string}
|
|
1610
|
+
*/
|
|
1611
|
+
path;
|
|
1612
|
+
/**
|
|
1613
|
+
* The trace of the response.
|
|
1614
|
+
* @type {string}
|
|
1615
|
+
*/
|
|
1616
|
+
trace;
|
|
1617
|
+
/**
|
|
1618
|
+
*
|
|
1619
|
+
* Create a new REST error.
|
|
1620
|
+
*/
|
|
1621
|
+
constructor({ timestamp, status, error, trace, message, path }) {
|
|
1622
|
+
super(`Rest request failed with response code: ${status}${message ? ` | message: ${message}` : ""}`);
|
|
1623
|
+
this.name = "Hoshimi [RestError]";
|
|
1624
|
+
this.timestamp = timestamp;
|
|
1625
|
+
this.status = status;
|
|
1626
|
+
this.error = error;
|
|
1627
|
+
this.trace = trace;
|
|
1628
|
+
this.message = message;
|
|
1629
|
+
this.path = path;
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
var Rest = class {
|
|
1633
|
+
/**
|
|
1634
|
+
* The URL for the REST.
|
|
1635
|
+
* @type {string}
|
|
1636
|
+
*/
|
|
1637
|
+
url;
|
|
1638
|
+
/**
|
|
1639
|
+
* The version for the REST.
|
|
1640
|
+
* @type {string}
|
|
1641
|
+
*/
|
|
1642
|
+
version = "v4";
|
|
1643
|
+
/**
|
|
1644
|
+
* The timeout for the REST.
|
|
1645
|
+
* @type {number}
|
|
1646
|
+
*/
|
|
1647
|
+
restTimeout;
|
|
1648
|
+
/**
|
|
1649
|
+
* The user agent for the REST.
|
|
1650
|
+
* @type {UserAgent}
|
|
1651
|
+
*/
|
|
1652
|
+
userAgent;
|
|
1653
|
+
/**
|
|
1654
|
+
* The node for the REST.
|
|
1655
|
+
* @type {Node}
|
|
1656
|
+
*/
|
|
1657
|
+
node;
|
|
1658
|
+
/**
|
|
1659
|
+
*
|
|
1660
|
+
* Create a new REST.
|
|
1661
|
+
* @param {NodeStructure} node The node for the REST.
|
|
1662
|
+
* @example
|
|
1663
|
+
* ```ts
|
|
1664
|
+
* const node = new Node({
|
|
1665
|
+
* host: "localhost",
|
|
1666
|
+
* port: 2333,
|
|
1667
|
+
* password: "youshallnotpass",
|
|
1668
|
+
* secure: false,
|
|
1669
|
+
* });
|
|
1670
|
+
*
|
|
1671
|
+
* const rest = new Rest(node);
|
|
1672
|
+
* console.log(rest.restUrl); // http://localhost:2333/v4
|
|
1673
|
+
* ```
|
|
1674
|
+
*/
|
|
1675
|
+
constructor(node) {
|
|
1676
|
+
const manager = node.nodeManager.manager;
|
|
1677
|
+
this.url = `${node.options.secure ? "https" : "http"}://${node.options.host}:${node.options.port}/${this.version}`;
|
|
1678
|
+
this.restTimeout = node.options.restTimeout ?? manager.options.restOptions.resumeTimeout ?? 1e4;
|
|
1679
|
+
this.userAgent = manager.options.nodeOptions.userAgent ?? HoshimiAgent;
|
|
1680
|
+
this.node = node;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* The REST URL to make requests.
|
|
1684
|
+
* @type {string}
|
|
1685
|
+
*/
|
|
1686
|
+
get restUrl() {
|
|
1687
|
+
return this.url;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* The session id of the node.
|
|
1691
|
+
* @type {string}
|
|
1692
|
+
*/
|
|
1693
|
+
get sessionId() {
|
|
1694
|
+
return this.node.sessionId;
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
*
|
|
1698
|
+
* Make a request to the node.
|
|
1699
|
+
* @param {RestOptions} options The options to make the request.
|
|
1700
|
+
* @returns {Promise<T | null>} The response from the node.
|
|
1701
|
+
*/
|
|
1702
|
+
async request(options) {
|
|
1703
|
+
const headers = {
|
|
1704
|
+
...options.headers,
|
|
1705
|
+
"Content-Type": "application/json",
|
|
1706
|
+
"User-Agent": this.userAgent,
|
|
1707
|
+
Authorization: this.node.options.password
|
|
1708
|
+
};
|
|
1709
|
+
options.method ??= "GET" /* Get */;
|
|
1710
|
+
const url = new URL(`${this.url}${options.endpoint}`);
|
|
1711
|
+
if (options.params) {
|
|
1712
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
1713
|
+
url.searchParams.append(key, value);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
url.searchParams.append("trace", "true");
|
|
1717
|
+
const abortController = new AbortController();
|
|
1718
|
+
const timeout = setTimeout(() => abortController.abort(), this.restTimeout);
|
|
1719
|
+
const fetchOptions = {
|
|
1720
|
+
headers,
|
|
1721
|
+
method: options.method,
|
|
1722
|
+
signal: abortController.signal
|
|
1723
|
+
};
|
|
1724
|
+
if (!["GET" /* Get */, "HEAD" /* Head */].includes(options.method) && options.body) {
|
|
1725
|
+
if (typeof options.body === "string") fetchOptions.body = options.body;
|
|
1726
|
+
else fetchOptions.body = JSON.stringify(options.body);
|
|
1727
|
+
}
|
|
1728
|
+
this.node.nodeManager.manager.emit(
|
|
1729
|
+
"debug" /* Debug */,
|
|
1730
|
+
4 /* Rest */,
|
|
1731
|
+
`[Rest] -> [${this.node.id} : ${options.method}]: Url: ${this.restUrl} | Endpoint: ${options.endpoint} | Params: ${url.search} | Body: ${options.body ? JSON.stringify(options.body) : "None"} | Headers: ${JSON.stringify(headers)}`
|
|
1732
|
+
);
|
|
1733
|
+
const response = await fetch(url.toString(), fetchOptions).finally(() => clearTimeout(timeout));
|
|
1734
|
+
if (!response.ok) {
|
|
1735
|
+
const restError = await response.json().catch(() => null);
|
|
1736
|
+
throw new RestError(
|
|
1737
|
+
restError ?? {
|
|
1738
|
+
timestamp: Date.now(),
|
|
1739
|
+
status: response.status,
|
|
1740
|
+
error: "Unknown Error",
|
|
1741
|
+
message: "Unexpected error response from Lavalink server",
|
|
1742
|
+
path: options.endpoint
|
|
1743
|
+
}
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
if (response.status === 204 /* NoContent */) return null;
|
|
1747
|
+
return response.json().catch(() => null);
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
*
|
|
1751
|
+
* Update the player data.
|
|
1752
|
+
* @param {Partial<UpdatePlayerInfo>} data The player data to update.
|
|
1753
|
+
* @returns {LavalinkPlayer | null} The updated player data.
|
|
1754
|
+
* @example
|
|
1755
|
+
* ```ts
|
|
1756
|
+
* const player = await node.rest.updatePlayer({
|
|
1757
|
+
* guildId: "guildId",
|
|
1758
|
+
* noReplace: true,
|
|
1759
|
+
* playerOptions: {
|
|
1760
|
+
* paused: false,
|
|
1761
|
+
* track: { encoded: "encoded track" },
|
|
1762
|
+
* },
|
|
1763
|
+
* });
|
|
1764
|
+
*
|
|
1765
|
+
* console.log(player); // The updated lavalink player data
|
|
1766
|
+
* ```
|
|
1767
|
+
*/
|
|
1768
|
+
updatePlayer(data) {
|
|
1769
|
+
if (!this.sessionId) return Promise.resolve(null);
|
|
1770
|
+
this.node.nodeManager.manager.emit(
|
|
1771
|
+
"debug" /* Debug */,
|
|
1772
|
+
4 /* Rest */,
|
|
1773
|
+
`[Rest] -> [${this.node.id}]: Updated player data for guild: ${data.guildId} | Payload: ${JSON.stringify(data)}`
|
|
1774
|
+
);
|
|
1775
|
+
validatePlayerData.call(this.node, data);
|
|
1776
|
+
return this.request({
|
|
1777
|
+
method: "PATCH" /* Patch */,
|
|
1778
|
+
endpoint: `/sessions/${this.sessionId}/players/${data.guildId}`,
|
|
1779
|
+
body: { ...data.playerOptions },
|
|
1780
|
+
params: { noReplace: `${data.noReplace ?? false}` }
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
*
|
|
1785
|
+
* Stop the track in player for the guild.
|
|
1786
|
+
* @param {string} guildId the guild id to stop the player
|
|
1787
|
+
* @returns {Promise<LavalinkPlayer | null>} The updated player data.
|
|
1788
|
+
* @example
|
|
1789
|
+
* ```ts
|
|
1790
|
+
* const player = await node.rest.stopPlayer("guildId");
|
|
1791
|
+
* if (player) console.log(player); // The lavalink player
|
|
1792
|
+
* ```
|
|
1793
|
+
*/
|
|
1794
|
+
stopPlayer(guildId) {
|
|
1795
|
+
if (!this.sessionId) return Promise.resolve(null);
|
|
1796
|
+
this.node.nodeManager.manager.emit(
|
|
1797
|
+
"debug" /* Debug */,
|
|
1798
|
+
4 /* Rest */,
|
|
1799
|
+
`[Rest] -> [${this.node.id}]: Stopped player for guild: ${guildId}`
|
|
1800
|
+
);
|
|
1801
|
+
return this.updatePlayer({
|
|
1802
|
+
guildId,
|
|
1803
|
+
playerOptions: {
|
|
1804
|
+
paused: false,
|
|
1805
|
+
track: { encoded: null }
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
*
|
|
1811
|
+
* Destroy the player for the guild.
|
|
1812
|
+
* @param {string} guildId The guild id to destroy the player.
|
|
1813
|
+
* @returns {Promise<void>} The updated player data.
|
|
1814
|
+
* @example
|
|
1815
|
+
* ```ts
|
|
1816
|
+
* await node.rest.destroyPlayer("guildId");
|
|
1817
|
+
* ```
|
|
1818
|
+
* @example
|
|
1819
|
+
*/
|
|
1820
|
+
async destroyPlayer(guildId) {
|
|
1821
|
+
if (!this.sessionId) return;
|
|
1822
|
+
this.node.nodeManager.manager.emit(
|
|
1823
|
+
"debug" /* Debug */,
|
|
1824
|
+
4 /* Rest */,
|
|
1825
|
+
`[Rest] -> [${this.node.id}]: Destroyed player for guild: ${guildId}`
|
|
1826
|
+
);
|
|
1827
|
+
await this.request({
|
|
1828
|
+
method: "DELETE" /* Delete */,
|
|
1829
|
+
endpoint: `/sessions/${this.sessionId}/players/${guildId}`
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
*
|
|
1834
|
+
* Update the session for the node
|
|
1835
|
+
* @param {boolean} resuming Enable resuming for the session.
|
|
1836
|
+
* @param {number | null} timeout The timeout for the session.
|
|
1837
|
+
* @returns {Promise<LavalinkSession | null>} The updated session data.
|
|
1838
|
+
* @example
|
|
1839
|
+
* ```ts
|
|
1840
|
+
* const session = await node.rest.updateSession(true, 10000);
|
|
1841
|
+
* if (session) console.log(session); // The lavalink session data
|
|
1842
|
+
* ```
|
|
1843
|
+
*/
|
|
1844
|
+
updateSession(resuming, timeout = null) {
|
|
1845
|
+
if (!this.sessionId) return Promise.resolve(null);
|
|
1846
|
+
this.node.nodeManager.manager.emit(
|
|
1847
|
+
"debug" /* Debug */,
|
|
1848
|
+
4 /* Rest */,
|
|
1849
|
+
`[Rest] -> [${this.node.id}]: Updated session for resumed: ${resuming} | Timeout: ${timeout ?? "None"}`
|
|
1850
|
+
);
|
|
1851
|
+
return this.request({
|
|
1852
|
+
method: "PATCH" /* Patch */,
|
|
1853
|
+
endpoint: `/sessions/${this.sessionId}`,
|
|
1854
|
+
body: { resuming, timeout }
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
// src/classes/player/filters/DSPXPlugin.ts
|
|
1860
|
+
var DSPXPluginFilter = class {
|
|
1861
|
+
/**
|
|
1862
|
+
* The filter manager instance.
|
|
1863
|
+
* @type {FilterManagerStructure}
|
|
1864
|
+
* @readonly
|
|
1865
|
+
*/
|
|
1866
|
+
manager;
|
|
1867
|
+
/**
|
|
1868
|
+
* Create a new DSPXPluginFilter instance.
|
|
1869
|
+
* @param {FilterManagerStructure} manager The filter manager instance.
|
|
1870
|
+
*/
|
|
1871
|
+
constructor(manager) {
|
|
1872
|
+
this.manager = manager;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
*
|
|
1876
|
+
* Set the low-pass filter with the given settings.
|
|
1877
|
+
* @param {FilterPluginPassSettings} [settings=DefaultFilter.DSPXLowPass] The settings for the low-pass filter.
|
|
1878
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
1879
|
+
*/
|
|
1880
|
+
async setLowPass(settings = DefaultFilterPreset.DSPXLowPass) {
|
|
1881
|
+
validateNodePlugins(this.manager.player.node, ["lavadspx-plugin" /* LavaDspx */]);
|
|
1882
|
+
if (!this.manager.player.node.info?.filters?.includes("low-pass" /* DSPXLowpass */))
|
|
1883
|
+
throw new PlayerError("Node filters does not include the 'low-pass' filter. (Or the node doesn't have it enabled)");
|
|
1884
|
+
if (!this.manager.data) this.manager.data = {};
|
|
1885
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
1886
|
+
if (!this.manager.data.pluginFilters["low-pass"]) this.manager.data.pluginFilters["low-pass"] = {};
|
|
1887
|
+
if (this.manager.filters.lavalinkLavaDspxPlugin.lowPass) {
|
|
1888
|
+
delete this.manager.data.pluginFilters["low-pass"];
|
|
1889
|
+
} else {
|
|
1890
|
+
this.manager.data.pluginFilters["low-pass"] = { ...settings };
|
|
1891
|
+
}
|
|
1892
|
+
this.manager.filters.lavalinkLavaDspxPlugin.lowPass = !this.manager.filters.lavalinkLavaDspxPlugin.lowPass;
|
|
1893
|
+
await this.manager.apply();
|
|
1894
|
+
return this.manager.filters.lavalinkLavaDspxPlugin.lowPass;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
*
|
|
1898
|
+
* Set the high-pass filter with the given settings.
|
|
1899
|
+
* @param {FilterPluginPassSettings} [settings=DefaultFilter.DSPXHighPass] The settings for the high-pass filter.
|
|
1900
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
1901
|
+
*/
|
|
1902
|
+
async setHighPass(settings = DefaultFilterPreset.DSPXHighPass) {
|
|
1903
|
+
validateNodePlugins(this.manager.player.node, ["lavadspx-plugin" /* LavaDspx */]);
|
|
1904
|
+
if (this.manager.player.node.info && !this.manager.player.node.info?.filters?.includes("high-pass" /* DSPXHighpass */))
|
|
1905
|
+
throw new PlayerError("Node filters does not include the 'high-pass' filter. (Or the node doesn't have it enabled)");
|
|
1906
|
+
if (!this.manager.data) this.manager.data = {};
|
|
1907
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
1908
|
+
if (!this.manager.data.pluginFilters["high-pass"]) this.manager.data.pluginFilters["high-pass"] = {};
|
|
1909
|
+
if (this.manager.filters.lavalinkLavaDspxPlugin.highPass) {
|
|
1910
|
+
delete this.manager.data.pluginFilters["high-pass"];
|
|
1911
|
+
} else {
|
|
1912
|
+
this.manager.data.pluginFilters["high-pass"] = { ...settings };
|
|
1913
|
+
}
|
|
1914
|
+
this.manager.filters.lavalinkLavaDspxPlugin.highPass = !this.manager.filters.lavalinkLavaDspxPlugin.highPass;
|
|
1915
|
+
await this.manager.apply();
|
|
1916
|
+
return this.manager.filters.lavalinkLavaDspxPlugin.highPass;
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
*
|
|
1920
|
+
* Set the normalization filter with the given settings.
|
|
1921
|
+
* @param {NormalizationSettings} [settings=DefaultFilter.DSPXNormalization] The settings for the normalization filter.
|
|
1922
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
1923
|
+
*/
|
|
1924
|
+
async setNormalization(settings = DefaultFilterPreset.DSPXNormalization) {
|
|
1925
|
+
validateNodePlugins(this.manager.player.node, ["lavadspx-plugin" /* LavaDspx */]);
|
|
1926
|
+
if (!this.manager.player.node.info?.filters?.includes("normalization" /* DSPXNormalization */))
|
|
1927
|
+
throw new PlayerError("Node filters does not include the 'normalization' filter. (Or the node doesn't have it enabled)");
|
|
1928
|
+
if (!this.manager.data) this.manager.data = {};
|
|
1929
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
1930
|
+
if (!this.manager.data.pluginFilters.normalization) this.manager.data.pluginFilters.normalization = {};
|
|
1931
|
+
if (this.manager.filters.lavalinkLavaDspxPlugin.normalization) {
|
|
1932
|
+
delete this.manager.data.pluginFilters.normalization;
|
|
1933
|
+
} else {
|
|
1934
|
+
this.manager.data.pluginFilters.normalization = { ...settings };
|
|
1935
|
+
}
|
|
1936
|
+
this.manager.filters.lavalinkLavaDspxPlugin.normalization = !this.manager.filters.lavalinkLavaDspxPlugin.normalization;
|
|
1937
|
+
await this.manager.apply();
|
|
1938
|
+
return this.manager.filters.lavalinkLavaDspxPlugin.normalization;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
*
|
|
1942
|
+
* Set the echo filter with the given settings.
|
|
1943
|
+
* @param {EchoSettings} [settings=DefaultFilter.DSPXEcho] The settings for the echo filter.
|
|
1944
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
1945
|
+
*/
|
|
1946
|
+
async setEcho(settings = DefaultFilterPreset.DSPXEcho) {
|
|
1947
|
+
validateNodePlugins(this.manager.player.node, ["lavadspx-plugin" /* LavaDspx */]);
|
|
1948
|
+
if (!this.manager.player.node.info?.filters?.includes("echo" /* DSPXEcho */))
|
|
1949
|
+
throw new PlayerError("Node filters does not include the 'echo' filter. (Or the node doesn't have it enabled)");
|
|
1950
|
+
if (!this.manager.data) this.manager.data = {};
|
|
1951
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
1952
|
+
if (!this.manager.data.pluginFilters.echo) this.manager.data.pluginFilters.echo = {};
|
|
1953
|
+
if (this.manager.filters.lavalinkLavaDspxPlugin.echo) {
|
|
1954
|
+
delete this.manager.data.pluginFilters.echo;
|
|
1955
|
+
} else {
|
|
1956
|
+
this.manager.data.pluginFilters.echo = { ...settings };
|
|
1957
|
+
}
|
|
1958
|
+
this.manager.filters.lavalinkLavaDspxPlugin.echo = !this.manager.filters.lavalinkLavaDspxPlugin.echo;
|
|
1959
|
+
await this.manager.apply();
|
|
1960
|
+
return this.manager.filters.lavalinkLavaDspxPlugin.echo;
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
// src/classes/player/filters/LavalinkPlugin.ts
|
|
1965
|
+
var LavalinkPluginFilter = class {
|
|
1966
|
+
/**
|
|
1967
|
+
* The filter manager instance.
|
|
1968
|
+
* @type {FilterManagerStructure}
|
|
1969
|
+
* @private
|
|
1970
|
+
* @readonly
|
|
1971
|
+
*/
|
|
1972
|
+
manager;
|
|
1973
|
+
/**
|
|
1974
|
+
* Creates an instance of LavalinkPluginFilter.
|
|
1975
|
+
* @param {FilterManagerStructure} filters - The filter manager instance.
|
|
1976
|
+
*/
|
|
1977
|
+
constructor(filters) {
|
|
1978
|
+
this.manager = filters;
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
*
|
|
1982
|
+
* Set the echo filter with the given settings.
|
|
1983
|
+
* @param {Omit<EchoSettings, "echoLength">} [settings=DefaultFilter.PluginEcho] The settings for the echo filter.
|
|
1984
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
1985
|
+
*/
|
|
1986
|
+
async setEcho(settings = DefaultFilterPreset.PluginEcho) {
|
|
1987
|
+
validateNodePlugins(this.manager.player.node, ["lavalink-filter-plugin" /* FilterPlugin */]);
|
|
1988
|
+
if (!this.manager.player.node.info?.filters?.includes("echo" /* Echo */))
|
|
1989
|
+
throw new PlayerError("Node filters does not include the 'echo' filter. (Or the node doesn't have it enabled)");
|
|
1990
|
+
if (!this.manager.data) this.manager.data = {};
|
|
1991
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
1992
|
+
if (!this.manager.data.pluginFilters["lavalink-filter-plugin"])
|
|
1993
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"] = { echo: { decay: 0, delay: 0 }, reverb: { delays: [], gains: [] } };
|
|
1994
|
+
if (!this.manager.data.pluginFilters["lavalink-filter-plugin"].echo)
|
|
1995
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].echo = { decay: 0, delay: 0 };
|
|
1996
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].echo.delay = this.manager.filters.lavalinkFilterPlugin.echo ? 0 : settings.delay;
|
|
1997
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].echo.decay = this.manager.filters.lavalinkFilterPlugin.echo ? 0 : settings.decay;
|
|
1998
|
+
this.manager.filters.lavalinkFilterPlugin.echo = !this.manager.filters.lavalinkFilterPlugin.echo;
|
|
1999
|
+
await this.manager.apply();
|
|
2000
|
+
return this.manager.filters.lavalinkFilterPlugin.echo;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
*
|
|
2004
|
+
* Set the reverb filter with the given settings.
|
|
2005
|
+
* @param {Partial<LavalinkFilterPluginReverbSettings>} [settings=DefaultFilter.PluginReverb] The settings for the reverb filter.
|
|
2006
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2007
|
+
*/
|
|
2008
|
+
async setReverb(settings = DefaultFilterPreset.PluginReverb) {
|
|
2009
|
+
validateNodePlugins(this.manager.player.node, ["lavalink-filter-plugin" /* FilterPlugin */]);
|
|
2010
|
+
if (!this.manager.player.node.info?.filters?.includes("reverb" /* Reverb */))
|
|
2011
|
+
throw new PlayerError("Node filters does not include the 'reverb' filter. (Or the node doesn't have it enabled)");
|
|
2012
|
+
if (!this.manager.data) this.manager.data = {};
|
|
2013
|
+
if (!this.manager.data.pluginFilters) this.manager.data.pluginFilters = {};
|
|
2014
|
+
if (!this.manager.data.pluginFilters["lavalink-filter-plugin"])
|
|
2015
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"] = { echo: { decay: 0, delay: 0 }, reverb: { delays: [], gains: [] } };
|
|
2016
|
+
if (!this.manager.data.pluginFilters["lavalink-filter-plugin"].reverb)
|
|
2017
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].reverb = { delays: [], gains: [] };
|
|
2018
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].reverb.delays = this.manager.filters.lavalinkFilterPlugin.reverb ? [] : settings.delays;
|
|
2019
|
+
this.manager.data.pluginFilters["lavalink-filter-plugin"].reverb.gains = this.manager.filters.lavalinkFilterPlugin.reverb ? [] : settings.gains;
|
|
2020
|
+
this.manager.filters.lavalinkFilterPlugin.reverb = !this.manager.filters.lavalinkFilterPlugin.reverb;
|
|
2021
|
+
await this.manager.apply();
|
|
2022
|
+
return this.manager.filters.lavalinkFilterPlugin.reverb;
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
|
|
2026
|
+
// src/classes/player/filters/Manager.ts
|
|
2027
|
+
var FilterManager = class {
|
|
2028
|
+
/**
|
|
2029
|
+
* The player this filter manager belongs to.
|
|
2030
|
+
* @type {PlayerStructure}
|
|
2031
|
+
* @public
|
|
2032
|
+
* @readonly
|
|
2033
|
+
*/
|
|
2034
|
+
player;
|
|
2035
|
+
/**
|
|
2036
|
+
* The bands applied to the player.
|
|
2037
|
+
* @type {EQBandSettings[]}
|
|
2038
|
+
* @readonly
|
|
2039
|
+
*/
|
|
2040
|
+
bands = [];
|
|
2041
|
+
/**
|
|
2042
|
+
* The current filter settings applied to the player.
|
|
2043
|
+
* @type {FilterSettings}
|
|
2044
|
+
* @public
|
|
2045
|
+
*/
|
|
2046
|
+
data = { ...DefaultPlayerFilters };
|
|
2047
|
+
/**
|
|
2048
|
+
* The enabled filters for the player.
|
|
2049
|
+
* @type {EnabledPlayerFilters}
|
|
2050
|
+
*/
|
|
2051
|
+
filters = {
|
|
2052
|
+
audioOutput: "stereo" /* Stereo */,
|
|
2053
|
+
volume: false,
|
|
2054
|
+
vaporwave: false,
|
|
2055
|
+
custom: false,
|
|
2056
|
+
nightcore: false,
|
|
2057
|
+
rotation: false,
|
|
2058
|
+
karaoke: false,
|
|
2059
|
+
tremolo: false,
|
|
2060
|
+
vibrato: false,
|
|
2061
|
+
lowPass: false,
|
|
2062
|
+
distortion: false,
|
|
2063
|
+
timescale: false,
|
|
2064
|
+
lavalinkFilterPlugin: {
|
|
2065
|
+
echo: false,
|
|
2066
|
+
reverb: false
|
|
2067
|
+
},
|
|
2068
|
+
lavalinkLavaDspxPlugin: {
|
|
2069
|
+
lowPass: false,
|
|
2070
|
+
highPass: false,
|
|
2071
|
+
normalization: false,
|
|
2072
|
+
echo: false
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
/**
|
|
2076
|
+
* The lavalink plugin filters manager.
|
|
2077
|
+
* @type {LavalinkPluginFilter}
|
|
2078
|
+
* @readonly
|
|
2079
|
+
*/
|
|
2080
|
+
plugin;
|
|
2081
|
+
/**
|
|
2082
|
+
* The DSPX plugin filters manager.
|
|
2083
|
+
* @type {DSPXPluginFilter}
|
|
2084
|
+
* @readonly
|
|
2085
|
+
*/
|
|
2086
|
+
dspx;
|
|
2087
|
+
/**
|
|
2088
|
+
*
|
|
2089
|
+
* Creates a new filter manager.
|
|
2090
|
+
* @param {PlayerStructure} player The player this filter manager belongs to.
|
|
2091
|
+
*/
|
|
2092
|
+
constructor(player) {
|
|
2093
|
+
this.player = player;
|
|
2094
|
+
this.plugin = new LavalinkPluginFilter(this);
|
|
2095
|
+
this.dspx = new DSPXPluginFilter(this);
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
*
|
|
2099
|
+
* Checks if a custom filter is active.
|
|
2100
|
+
* @returns {boolean} True if a custom filter is active, false otherwise.
|
|
2101
|
+
*/
|
|
2102
|
+
isCustom() {
|
|
2103
|
+
this.filters.custom = !this.filters.nightcore && !this.filters.vaporwave && Object.values(this.data.timescale ?? {}).some((d) => d !== 1);
|
|
2104
|
+
return this.filters.custom;
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Resets all filters to their default values.
|
|
2108
|
+
* @returns {Promise<void>} A promise that resolves when the filters have been reset.
|
|
2109
|
+
*/
|
|
2110
|
+
async reset() {
|
|
2111
|
+
this.filters = {
|
|
2112
|
+
audioOutput: "stereo" /* Stereo */,
|
|
2113
|
+
volume: false,
|
|
2114
|
+
vaporwave: false,
|
|
2115
|
+
custom: false,
|
|
2116
|
+
nightcore: false,
|
|
2117
|
+
rotation: false,
|
|
2118
|
+
karaoke: false,
|
|
2119
|
+
tremolo: false,
|
|
2120
|
+
vibrato: false,
|
|
2121
|
+
lowPass: false,
|
|
2122
|
+
distortion: false,
|
|
2123
|
+
timescale: false,
|
|
2124
|
+
lavalinkFilterPlugin: {
|
|
2125
|
+
echo: false,
|
|
2126
|
+
reverb: false
|
|
2127
|
+
},
|
|
2128
|
+
lavalinkLavaDspxPlugin: {
|
|
2129
|
+
lowPass: false,
|
|
2130
|
+
highPass: false,
|
|
2131
|
+
normalization: false,
|
|
2132
|
+
echo: false
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
for (const [key, value] of Object.entries(DefaultPlayerFilters)) {
|
|
2136
|
+
this.data[key] = value;
|
|
2137
|
+
}
|
|
2138
|
+
await this.apply();
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
*
|
|
2142
|
+
* Applies the current filters to the player.
|
|
2143
|
+
* @returns {Promise<void>} A promise that resolves when the filters have been applied.
|
|
2144
|
+
*/
|
|
2145
|
+
async apply() {
|
|
2146
|
+
if (!this.player.node.sessionId) return;
|
|
2147
|
+
this.check();
|
|
2148
|
+
this.isCustom();
|
|
2149
|
+
const filters = { ...this.data };
|
|
2150
|
+
if (!this.filters.volume) delete filters.volume;
|
|
2151
|
+
if (!this.filters.tremolo) delete filters.tremolo;
|
|
2152
|
+
if (!this.filters.vibrato) delete filters.vibrato;
|
|
2153
|
+
if (!this.filters.lavalinkFilterPlugin.echo) delete filters.pluginFilters?.["lavalink-filter-plugin"]?.echo;
|
|
2154
|
+
if (!this.filters.lavalinkFilterPlugin.reverb) delete filters.pluginFilters?.["lavalink-filter-plugin"]?.reverb;
|
|
2155
|
+
if (!this.filters.lavalinkLavaDspxPlugin.echo) delete filters.pluginFilters?.echo;
|
|
2156
|
+
if (!this.filters.lavalinkLavaDspxPlugin.normalization) delete filters.pluginFilters?.normalization;
|
|
2157
|
+
if (!this.filters.lavalinkLavaDspxPlugin.highPass) delete filters.pluginFilters?.["high-pass"];
|
|
2158
|
+
if (!this.filters.lavalinkLavaDspxPlugin.lowPass) delete filters.pluginFilters?.["low-pass"];
|
|
2159
|
+
if (filters.pluginFilters?.["lavalink-filter-plugin"] && !Object.values(filters.pluginFilters["lavalink-filter-plugin"]).length)
|
|
2160
|
+
delete filters.pluginFilters["lavalink-filter-plugin"];
|
|
2161
|
+
if (filters.pluginFilters && Object.values(filters.pluginFilters).length === 0) delete filters.pluginFilters;
|
|
2162
|
+
if (this.filters.audioOutput === "stereo" /* Stereo */) delete filters.channelMix;
|
|
2163
|
+
if (!this.filters.lowPass) delete filters.lowPass;
|
|
2164
|
+
if (!this.filters.karaoke) delete filters.karaoke;
|
|
2165
|
+
if (!this.filters.rotation) delete filters.rotation;
|
|
2166
|
+
if (!this.filters.distortion) delete filters.distortion;
|
|
2167
|
+
if (!this.filters.timescale) delete filters.timescale;
|
|
2168
|
+
if (this.data.timescale && Object.values(this.data.timescale).every((v) => v === 1)) delete filters.timescale;
|
|
2169
|
+
filters.equalizer = [...this.bands];
|
|
2170
|
+
if (!filters.equalizer.length) delete filters.equalizer;
|
|
2171
|
+
for (const key in filters) {
|
|
2172
|
+
if (!this.player.node.info?.filters?.includes(key)) delete filters[key];
|
|
2173
|
+
}
|
|
2174
|
+
await this.player.updatePlayer({ playerOptions: { filters } });
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Checks if the current filters are active.
|
|
2178
|
+
* @param {TimescaleSettings} timescale The timescale settings to check against.
|
|
2179
|
+
* @returns {void} Nothing!
|
|
2180
|
+
*/
|
|
2181
|
+
check(timescale) {
|
|
2182
|
+
this.filters.rotation = this.data.rotation?.rotationHz !== 0;
|
|
2183
|
+
this.filters.vibrato = this.data.vibrato?.frequency !== 0 || this.data.vibrato?.depth !== 0;
|
|
2184
|
+
this.filters.tremolo = this.data.tremolo?.frequency !== 0 || this.data.tremolo?.depth !== 0;
|
|
2185
|
+
const lavalinkPluginFilters = this.data.pluginFilters?.["lavalink-filter-plugin"] ?? {};
|
|
2186
|
+
this.filters.lavalinkFilterPlugin.echo = lavalinkPluginFilters.echo?.decay !== 0 || lavalinkPluginFilters.echo?.delay !== 0;
|
|
2187
|
+
this.filters.lavalinkFilterPlugin.reverb = lavalinkPluginFilters.reverb?.delays?.length !== 0 || lavalinkPluginFilters.reverb?.gains?.length !== 0;
|
|
2188
|
+
this.filters.lavalinkLavaDspxPlugin.highPass = Object.values(this.data.pluginFilters?.["high-pass"] ?? {}).length > 0;
|
|
2189
|
+
this.filters.lavalinkLavaDspxPlugin.lowPass = Object.values(this.data.pluginFilters?.["low-pass"] ?? {}).length > 0;
|
|
2190
|
+
this.filters.lavalinkLavaDspxPlugin.normalization = Object.values(this.data.pluginFilters?.normalization ?? {}).length > 0;
|
|
2191
|
+
this.filters.lavalinkLavaDspxPlugin.echo = Object.values(this.data.pluginFilters?.echo ?? {}).length > 0 && typeof this.data.pluginFilters?.echo?.delay === "undefined";
|
|
2192
|
+
this.filters.lowPass = this.data.lowPass?.smoothing !== 0;
|
|
2193
|
+
this.filters.karaoke = Object.values(this.data.karaoke ?? {}).some((v) => v !== 0);
|
|
2194
|
+
this.filters.distortion = Object.values(this.data.distortion ?? {}).some((v) => v !== 0 && v !== 1);
|
|
2195
|
+
this.filters.timescale = Object.values(this.data.timescale ?? {}).some((v) => v !== 1);
|
|
2196
|
+
if ((this.filters.nightcore || this.filters.vaporwave) && timescale) {
|
|
2197
|
+
if (timescale.pitch !== this.data.timescale?.pitch || timescale.rate !== this.data.timescale?.rate || timescale.speed !== this.data.timescale?.speed) {
|
|
2198
|
+
this.filters.custom = Object.values(this.data.timescale ?? {}).some((v) => v !== 1);
|
|
2199
|
+
this.filters.nightcore = false;
|
|
2200
|
+
this.filters.vaporwave = false;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
*
|
|
2206
|
+
* Checks if a specific filter is active.
|
|
2207
|
+
* @param {FilterType} filter The filter type to check.
|
|
2208
|
+
* @returns {boolean} True if the filter is active, false otherwise.
|
|
2209
|
+
*/
|
|
2210
|
+
has(filter) {
|
|
2211
|
+
const dspx = this.filters.lavalinkLavaDspxPlugin[filter];
|
|
2212
|
+
if (isValid(dspx)) return dspx;
|
|
2213
|
+
const plugin = this.filters.lavalinkFilterPlugin[filter];
|
|
2214
|
+
if (isValid(plugin)) return plugin;
|
|
2215
|
+
const kind = this.filters[filter];
|
|
2216
|
+
if (typeof kind === "boolean") return kind;
|
|
2217
|
+
if (typeof kind === "string") return kind !== "stereo" /* Stereo */;
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
*
|
|
2222
|
+
* Sets the volume for the player.
|
|
2223
|
+
* @param {number} volume The volume level to set (between 0 and 5).
|
|
2224
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the volume was changed, false otherwise.
|
|
2225
|
+
*/
|
|
2226
|
+
async setVolume(volume) {
|
|
2227
|
+
if (typeof volume !== "number" || Number.isNaN(volume) || volume < 0 || volume > 5)
|
|
2228
|
+
throw new PlayerError("Volume must be a number between 0 and 5.");
|
|
2229
|
+
this.data = { volume };
|
|
2230
|
+
this.filters.volume = volume !== 1;
|
|
2231
|
+
await this.apply();
|
|
2232
|
+
return this.filters.volume;
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Sets the audio output for the player.
|
|
2236
|
+
* @param {AudioOutput} output The audio output to set.
|
|
2237
|
+
* @returns {Promise<AudioOutput>} A promise that resolves to the set audio output.
|
|
2238
|
+
*/
|
|
2239
|
+
async setAudioOutput(output) {
|
|
2240
|
+
const outputs = Object.values(AudioOutput);
|
|
2241
|
+
if (!outputs.includes(output)) throw new PlayerError(`Audio output must be one of the following: ${outputs.join(", ")}.`);
|
|
2242
|
+
this.filters.audioOutput = output;
|
|
2243
|
+
this.data.channelMix = AudioOutputData[output];
|
|
2244
|
+
await this.apply();
|
|
2245
|
+
return this.filters.audioOutput;
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
*
|
|
2249
|
+
* Sets the speed for the player.
|
|
2250
|
+
* @param {number} speed The speed to set (default is 1).
|
|
2251
|
+
* @returns {Promise<boolean>} A promise that resolves to true if a custom filter is active, false otherwise.
|
|
2252
|
+
*/
|
|
2253
|
+
async setSpeed(speed = 1) {
|
|
2254
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2255
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2256
|
+
if (this.filters.nightcore || this.filters.vaporwave) {
|
|
2257
|
+
this.data.timescale = {
|
|
2258
|
+
speed: 1,
|
|
2259
|
+
pitch: 1,
|
|
2260
|
+
rate: 1
|
|
2261
|
+
};
|
|
2262
|
+
this.filters.nightcore = false;
|
|
2263
|
+
this.filters.vaporwave = false;
|
|
2264
|
+
}
|
|
2265
|
+
this.data.timescale.speed = speed;
|
|
2266
|
+
await this.apply();
|
|
2267
|
+
return this.filters.custom;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
*
|
|
2271
|
+
* Sets the rate for the player.
|
|
2272
|
+
* @param {number} rate The rate to set (default is 1).
|
|
2273
|
+
* @returns {Promise<boolean>} A promise that resolves to true if a custom filter is active, false otherwise.
|
|
2274
|
+
*/
|
|
2275
|
+
async setRate(rate = 1) {
|
|
2276
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2277
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2278
|
+
if (this.filters.nightcore || this.filters.vaporwave) {
|
|
2279
|
+
this.data.timescale = {
|
|
2280
|
+
speed: 1,
|
|
2281
|
+
pitch: 1,
|
|
2282
|
+
rate: 1
|
|
2283
|
+
};
|
|
2284
|
+
this.filters.nightcore = false;
|
|
2285
|
+
this.filters.vaporwave = false;
|
|
2286
|
+
}
|
|
2287
|
+
this.data.timescale.rate = rate;
|
|
2288
|
+
await this.apply();
|
|
2289
|
+
return this.filters.custom;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
*
|
|
2293
|
+
* Sets the pitch for the player.
|
|
2294
|
+
* @param {number} pitch The pitch
|
|
2295
|
+
* @returns {Promise<boolean>} A promise that resolves to true if a custom filter is active, false otherwise.
|
|
2296
|
+
*/
|
|
2297
|
+
async setPitch(pitch = 1) {
|
|
2298
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2299
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2300
|
+
if (this.filters.nightcore || this.filters.vaporwave) {
|
|
2301
|
+
this.data.timescale = {
|
|
2302
|
+
speed: 1,
|
|
2303
|
+
pitch: 1,
|
|
2304
|
+
rate: 1
|
|
2305
|
+
};
|
|
2306
|
+
this.filters.nightcore = false;
|
|
2307
|
+
this.filters.vaporwave = false;
|
|
2308
|
+
}
|
|
2309
|
+
this.data.timescale.pitch = pitch;
|
|
2310
|
+
await this.apply();
|
|
2311
|
+
return this.filters.custom;
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
*
|
|
2315
|
+
* Sets the EQ bands for the player.
|
|
2316
|
+
* @param {RestOrArray<EQBandSettings>} bands The EQ band settings to set.
|
|
2317
|
+
* @returns {Promise<this>} A promise that resolves to the instance of the manager.
|
|
2318
|
+
*/
|
|
2319
|
+
async setEQBand(...bands) {
|
|
2320
|
+
bands = bands.flat();
|
|
2321
|
+
if (!bands.length || !bands.every((band) => typeof band.band === "number" && typeof band.gain === "number"))
|
|
2322
|
+
throw new PlayerError("Bands must be a non-empty object array containing 'band' and 'gain' properties.");
|
|
2323
|
+
for (const { band, gain } of bands) this.bands[band] = { band, gain };
|
|
2324
|
+
await this.apply();
|
|
2325
|
+
return this;
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
*
|
|
2329
|
+
* Clears all EQ bands for the player.
|
|
2330
|
+
* @returns {Promise<this>} A promise that resolves to the instance of the manager.
|
|
2331
|
+
*/
|
|
2332
|
+
async clearEQBands() {
|
|
2333
|
+
return this.setEQBand(Array.from({ length: 15 }, (_, i) => ({ band: i, gain: 0 })));
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
*
|
|
2337
|
+
* Set the vibrato filter with the given settings.
|
|
2338
|
+
* @param {TremoloSettings} [settings=DefaultFilterPreset.Vibrato] The settings for the vibrato filter.
|
|
2339
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2340
|
+
*/
|
|
2341
|
+
async setVibrato(settings = DefaultFilterPreset.Vibrato) {
|
|
2342
|
+
if (!this.player.node.info?.filters?.includes("vibrato" /* Vibrato */))
|
|
2343
|
+
throw new PlayerError("Node filters does not include the 'vibrato' filter. (Or the node doesn't have it enabled)");
|
|
2344
|
+
this.data.vibrato = {
|
|
2345
|
+
frequency: this.filters.vibrato ? 0 : settings.frequency,
|
|
2346
|
+
depth: this.filters.vibrato ? 0 : settings.depth
|
|
2347
|
+
};
|
|
2348
|
+
this.filters.vibrato = !this.filters.vibrato;
|
|
2349
|
+
await this.apply();
|
|
2350
|
+
return this.filters.vibrato;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
*
|
|
2354
|
+
* Set the tremolo filter with the given settings.
|
|
2355
|
+
* @param {TremoloSettings} [settings=DefaultFilterPreset.Tremolo] The settings for the tremolo filter.
|
|
2356
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2357
|
+
*/
|
|
2358
|
+
async setTremolo(settings = DefaultFilterPreset.Tremolo) {
|
|
2359
|
+
if (!this.player.node.info?.filters?.includes("tremolo" /* Tremolo */))
|
|
2360
|
+
throw new PlayerError("Node filters does not include the 'tremolo' filter. (Or the node doesn't have it enabled)");
|
|
2361
|
+
this.data.tremolo = {
|
|
2362
|
+
frequency: this.filters.tremolo ? 0 : settings.frequency,
|
|
2363
|
+
depth: this.filters.tremolo ? 0 : settings.depth
|
|
2364
|
+
};
|
|
2365
|
+
this.filters.tremolo = !this.filters.tremolo;
|
|
2366
|
+
await this.apply();
|
|
2367
|
+
return this.filters.tremolo;
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
*
|
|
2371
|
+
* Set the low-pass filter with the given settings.
|
|
2372
|
+
* @param {LowPassSettings} [settings=DefaultFilterPreset.Lowpass] The settings for the low-pass filter.
|
|
2373
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2374
|
+
*/
|
|
2375
|
+
async setLowPass(settings = DefaultFilterPreset.Lowpass) {
|
|
2376
|
+
if (!this.player.node.info?.filters?.includes("lowPass" /* LowPass */))
|
|
2377
|
+
throw new PlayerError("Node filters does not include the 'lowPass' filter. (Or the node doesn't have it enabled)");
|
|
2378
|
+
this.data.lowPass = { smoothing: this.filters.lowPass ? 0 : settings.smoothing };
|
|
2379
|
+
this.filters.lowPass = !this.filters.lowPass;
|
|
2380
|
+
await this.apply();
|
|
2381
|
+
return this.filters.lowPass;
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Set the nightcore filter with the given settings.
|
|
2385
|
+
* @param {Partial<TimescaleSettings>} [settings=DefaultFilterPreset.Nightcore] The settings for the nightcore filter.
|
|
2386
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2387
|
+
*/
|
|
2388
|
+
async setNightcore(settings = DefaultFilterPreset.Nightcore) {
|
|
2389
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2390
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2391
|
+
this.data.timescale = {
|
|
2392
|
+
speed: this.filters.nightcore ? 1 : settings.speed,
|
|
2393
|
+
pitch: this.filters.nightcore ? 1 : settings.pitch,
|
|
2394
|
+
rate: this.filters.nightcore ? 1 : settings.rate
|
|
2395
|
+
};
|
|
2396
|
+
this.filters.nightcore = !this.filters.nightcore;
|
|
2397
|
+
this.filters.vaporwave = false;
|
|
2398
|
+
this.filters.custom = false;
|
|
2399
|
+
await this.apply();
|
|
2400
|
+
return this.filters.nightcore;
|
|
2401
|
+
}
|
|
2402
|
+
/**
|
|
2403
|
+
*
|
|
2404
|
+
* Set the vaporwave filter with the given settings.
|
|
2405
|
+
* @param {Partial<TimescaleSettings>} [settings=DefaultFilterPreset.Vaporwave] The settings for the vaporwave filter.
|
|
2406
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2407
|
+
*/
|
|
2408
|
+
async setVaporwave(settings = DefaultFilterPreset.Vaporwave) {
|
|
2409
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2410
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2411
|
+
this.data.timescale = {
|
|
2412
|
+
speed: this.filters.vaporwave ? 1 : settings.speed,
|
|
2413
|
+
pitch: this.filters.vaporwave ? 1 : settings.pitch,
|
|
2414
|
+
rate: this.filters.vaporwave ? 1 : settings.rate
|
|
2415
|
+
};
|
|
2416
|
+
this.filters.vaporwave = !this.filters.vaporwave;
|
|
2417
|
+
this.filters.nightcore = false;
|
|
2418
|
+
this.filters.custom = false;
|
|
2419
|
+
await this.apply();
|
|
2420
|
+
return this.filters.vaporwave;
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
*
|
|
2424
|
+
* Set the karaoke filter with the given settings.
|
|
2425
|
+
* @param {KaraokeSettings} [settings=DefaultFilterPreset.Karaoke] The settings for the karaoke filter.
|
|
2426
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2427
|
+
*/
|
|
2428
|
+
async setKaraoke(settings = DefaultFilterPreset.Karaoke) {
|
|
2429
|
+
if (!this.player.node.info?.filters?.includes("karaoke" /* Karaoke */))
|
|
2430
|
+
throw new PlayerError("Node filters does not include the 'karaoke' filter. (Or the node doesn't have it enabled)");
|
|
2431
|
+
this.data.karaoke = {
|
|
2432
|
+
level: this.data.karaoke.level ? 0 : settings.level,
|
|
2433
|
+
monoLevel: this.data.karaoke.monoLevel ? 0 : settings.monoLevel,
|
|
2434
|
+
filterBand: this.data.karaoke.filterBand ? 0 : settings.filterBand,
|
|
2435
|
+
filterWidth: this.data.karaoke.filterWidth ? 0 : settings.filterWidth
|
|
2436
|
+
};
|
|
2437
|
+
this.filters.karaoke = !this.filters.karaoke;
|
|
2438
|
+
await this.apply();
|
|
2439
|
+
return this.filters.karaoke;
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
*
|
|
2443
|
+
* Set the distortion filter with the given settings.
|
|
2444
|
+
* @param {Partial<DistortionSettings>} [settings=DefaultFilterPreset.Distortion] The settings for the distortion filter.
|
|
2445
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2446
|
+
*/
|
|
2447
|
+
async setDistortion(settings = DefaultFilterPreset.Distortion) {
|
|
2448
|
+
if (!this.player.node.info?.filters?.includes("distortion" /* Distortion */))
|
|
2449
|
+
throw new PlayerError("Node filters does not include the 'distortion' filter. (Or the node doesn't have it enabled)");
|
|
2450
|
+
this.data.distortion = {
|
|
2451
|
+
sinOffset: this.filters.distortion ? 0 : settings.sinOffset,
|
|
2452
|
+
sinScale: this.filters.distortion ? 1 : settings.sinScale,
|
|
2453
|
+
cosOffset: this.filters.distortion ? 0 : settings.cosOffset,
|
|
2454
|
+
cosScale: this.filters.distortion ? 1 : settings.cosScale,
|
|
2455
|
+
tanOffset: this.filters.distortion ? 0 : settings.tanOffset,
|
|
2456
|
+
offset: this.filters.distortion ? 0 : settings.offset,
|
|
2457
|
+
scale: this.filters.distortion ? 1 : settings.scale
|
|
2458
|
+
};
|
|
2459
|
+
this.filters.distortion = !this.filters.distortion;
|
|
2460
|
+
await this.apply();
|
|
2461
|
+
return this.filters.distortion;
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Set the timescale filter with the given settings.
|
|
2465
|
+
* @param {Partial<TimescaleSettings>} settings The timescale settings to set.
|
|
2466
|
+
* @returns {Promise<boolean>} Whether the filter is now active.
|
|
2467
|
+
*/
|
|
2468
|
+
async setTimescale(settings) {
|
|
2469
|
+
if (!this.player.node.info?.filters?.includes("timescale" /* Timescale */))
|
|
2470
|
+
throw new PlayerError("Node filters does not include the 'timescale' filter. (Or the node doesn't have it enabled)");
|
|
2471
|
+
this.data.timescale = {
|
|
2472
|
+
pitch: settings.pitch ?? 1,
|
|
2473
|
+
rate: settings.rate ?? 1,
|
|
2474
|
+
speed: settings.speed ?? 1
|
|
2475
|
+
};
|
|
2476
|
+
this.filters.timescale = !this.filters.timescale;
|
|
2477
|
+
await this.apply();
|
|
2478
|
+
return this.filters.timescale;
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
|
|
2482
|
+
// src/classes/player/Storage.ts
|
|
2483
|
+
var PlayerStorage = class {
|
|
2484
|
+
/**
|
|
2485
|
+
* The internal storage.
|
|
2486
|
+
* @type {Map<K, V>}
|
|
2487
|
+
* @private
|
|
2488
|
+
* @readonly
|
|
2489
|
+
* @internal
|
|
2490
|
+
*/
|
|
2491
|
+
internal = /* @__PURE__ */ new Map();
|
|
2492
|
+
/**
|
|
2493
|
+
*
|
|
2494
|
+
* Get the value for a key in the storage.
|
|
2495
|
+
* @param {K} key The key to get the value for.
|
|
2496
|
+
* @returns {V | undefined} The value for the key, or undefined if it doesn't exist.
|
|
2497
|
+
*/
|
|
2498
|
+
get(key) {
|
|
2499
|
+
return this.internal.get(key);
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Set the value for a key in the storage.
|
|
2503
|
+
* @param {K} key The key to set the value for.
|
|
2504
|
+
* @param {V} value The value to set for the key.
|
|
2505
|
+
*/
|
|
2506
|
+
set(key, value) {
|
|
2507
|
+
this.internal.set(key, value);
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Check if the storage has a key.
|
|
2511
|
+
* @param {K} key The key to check for.
|
|
2512
|
+
* @returns {boolean} True if the storage has the key, false otherwise.
|
|
2513
|
+
*/
|
|
2514
|
+
has(key) {
|
|
2515
|
+
return this.internal.has(key);
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Delete a key from the storage.
|
|
2519
|
+
* @param {K} key The key to delete.
|
|
2520
|
+
* @returns {boolean} True if the key was deleted, false otherwise.
|
|
2521
|
+
*/
|
|
2522
|
+
delete(key) {
|
|
2523
|
+
return this.internal.delete(key);
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Get all keys in the storage.
|
|
2527
|
+
* @returns {K[]} The keys in the storage.
|
|
2528
|
+
*/
|
|
2529
|
+
keys() {
|
|
2530
|
+
return [...this.internal.keys()];
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Get all values in the storage.
|
|
2534
|
+
* @returns {V[]} The values in the storage.
|
|
2535
|
+
*/
|
|
2536
|
+
values() {
|
|
2537
|
+
return [...this.internal.values()];
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Get all entries in the storage.
|
|
2541
|
+
* @returns {[K, V][]} The entries in the storage.
|
|
2542
|
+
*/
|
|
2543
|
+
entries() {
|
|
2544
|
+
return [...this.internal.entries()];
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
*
|
|
2548
|
+
* Get all key-value pairs in the storage.
|
|
2549
|
+
* @returns {Record<K[number], V>} An object containing all key-value pairs in the storage, excluding internal keys.
|
|
2550
|
+
*/
|
|
2551
|
+
all() {
|
|
2552
|
+
return Object.fromEntries([...this.internal.entries()].filter(([key]) => !key.startsWith("internal_")));
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Clear the storage.
|
|
2556
|
+
*/
|
|
2557
|
+
clear() {
|
|
2558
|
+
this.internal.clear();
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Get the size of the storage.
|
|
2562
|
+
* @returns {number} The number of entries in the storage.
|
|
2563
|
+
*/
|
|
2564
|
+
get size() {
|
|
2565
|
+
return this.internal.size;
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
// src/classes/player/Player.ts
|
|
2570
|
+
var Player = class {
|
|
2571
|
+
/**
|
|
2572
|
+
* The data for the player.
|
|
2573
|
+
* @type {PlayerStorage}
|
|
2574
|
+
* @readonly
|
|
2575
|
+
*/
|
|
2576
|
+
data = new PlayerStorage();
|
|
2577
|
+
/**
|
|
2578
|
+
* The options for the player.
|
|
2579
|
+
* @type {PlayerOptions}
|
|
2580
|
+
* @readonly
|
|
2581
|
+
*/
|
|
2582
|
+
options;
|
|
2583
|
+
/**
|
|
2584
|
+
* The manager for the player.
|
|
2585
|
+
* @type {Hoshimi}
|
|
2586
|
+
* @readonly
|
|
2587
|
+
*/
|
|
2588
|
+
manager;
|
|
2589
|
+
/**
|
|
2590
|
+
* The queue for the player.
|
|
2591
|
+
* @type {Queue}
|
|
2592
|
+
* @readonly
|
|
2593
|
+
*/
|
|
2594
|
+
queue;
|
|
2595
|
+
/**
|
|
2596
|
+
* The filter manager for the player.
|
|
2597
|
+
* @type {FilterManager}
|
|
2598
|
+
* @readonly
|
|
2599
|
+
*/
|
|
2600
|
+
filterManager;
|
|
2601
|
+
/**
|
|
2602
|
+
* The node for the player.
|
|
2603
|
+
* @type {NodeStructure}
|
|
2604
|
+
*/
|
|
2605
|
+
node;
|
|
2606
|
+
/**
|
|
2607
|
+
* Check if the player is self deafened.
|
|
2608
|
+
* @type {boolean}
|
|
2609
|
+
*/
|
|
2610
|
+
selfDeaf = false;
|
|
2611
|
+
/**
|
|
2612
|
+
* Check if the player is self muted.
|
|
2613
|
+
* @type {boolean}
|
|
2614
|
+
*/
|
|
2615
|
+
selfMute = false;
|
|
2616
|
+
/**
|
|
2617
|
+
* Loop mode of the player.
|
|
2618
|
+
* @type {LoopMode}
|
|
2619
|
+
* @default LoopMode.Off
|
|
2620
|
+
*/
|
|
2621
|
+
loop = 3 /* Off */;
|
|
2622
|
+
/**
|
|
2623
|
+
* Check if the player is playing.
|
|
2624
|
+
* @type {boolean}
|
|
2625
|
+
* @default false
|
|
2626
|
+
*/
|
|
2627
|
+
playing = false;
|
|
2628
|
+
/**
|
|
2629
|
+
* Check if the player is paused.
|
|
2630
|
+
* @type {boolean}
|
|
2631
|
+
* @default false
|
|
2632
|
+
*/
|
|
2633
|
+
paused = false;
|
|
2634
|
+
/**
|
|
2635
|
+
* Check if the player is connected.
|
|
2636
|
+
* @type {boolean}
|
|
2637
|
+
* @default false
|
|
2638
|
+
*/
|
|
2639
|
+
connected = false;
|
|
2640
|
+
/**
|
|
2641
|
+
* Volume of the player.
|
|
2642
|
+
* @type {number}
|
|
2643
|
+
* @default 100
|
|
2644
|
+
*/
|
|
2645
|
+
volume = 100;
|
|
2646
|
+
/**
|
|
2647
|
+
* Guild ig of the player.
|
|
2648
|
+
* @type {string}
|
|
2649
|
+
*/
|
|
2650
|
+
guildId;
|
|
2651
|
+
/**
|
|
2652
|
+
* Voice channel idof the player.
|
|
2653
|
+
* @type {string | undefined}
|
|
2654
|
+
*/
|
|
2655
|
+
voiceId = void 0;
|
|
2656
|
+
/**
|
|
2657
|
+
* Text channel id of the player.
|
|
2658
|
+
* @type {string | undefined}
|
|
2659
|
+
*/
|
|
2660
|
+
textId = void 0;
|
|
2661
|
+
/**
|
|
2662
|
+
* The ping of the player.
|
|
2663
|
+
* @type {number}
|
|
2664
|
+
*/
|
|
2665
|
+
ping = 0;
|
|
2666
|
+
/**
|
|
2667
|
+
* The timestamp when the player was created.
|
|
2668
|
+
* @type {number}
|
|
2669
|
+
*/
|
|
2670
|
+
createdTimestamp = 0;
|
|
2671
|
+
/**
|
|
2672
|
+
* The position of the player.
|
|
2673
|
+
* @type {number}
|
|
2674
|
+
*/
|
|
2675
|
+
position = 0;
|
|
2676
|
+
/**
|
|
2677
|
+
* The voice connection details.
|
|
2678
|
+
* @type {PlayerVoice}
|
|
2679
|
+
*/
|
|
2680
|
+
voice = {
|
|
2681
|
+
endpoint: null,
|
|
2682
|
+
sessionId: null,
|
|
2683
|
+
token: null
|
|
2684
|
+
};
|
|
2685
|
+
/**
|
|
2686
|
+
*
|
|
2687
|
+
* Create a new player.
|
|
2688
|
+
* @param {Hoshimi} manager The manager for the player.
|
|
2689
|
+
* @param {PlayOptions} options The options for the player.
|
|
2690
|
+
* @example
|
|
2691
|
+
* ```ts
|
|
2692
|
+
* const player = new Player(manager, {
|
|
2693
|
+
* guildId: "guildId",
|
|
2694
|
+
* voiceId: "voiceId",
|
|
2695
|
+
* textId: "textId",
|
|
2696
|
+
* selfDeaf: true,
|
|
2697
|
+
* selfMute: false,
|
|
2698
|
+
* volume: 100,
|
|
2699
|
+
* });
|
|
2700
|
+
*
|
|
2701
|
+
* console.log(player.guildId); // guildId
|
|
2702
|
+
* console.log(player.voiceId); // voiceId
|
|
2703
|
+
* console.log(player.textId); // textId
|
|
2704
|
+
*/
|
|
2705
|
+
constructor(manager, options) {
|
|
2706
|
+
this.manager = manager;
|
|
2707
|
+
this.options = options;
|
|
2708
|
+
this.guildId = options.guildId;
|
|
2709
|
+
this.voiceId = options.voiceId;
|
|
2710
|
+
this.selfDeaf = options.selfDeaf ?? true;
|
|
2711
|
+
this.selfMute = options.selfMute ?? false;
|
|
2712
|
+
this.volume = options.volume ?? 100;
|
|
2713
|
+
this.textId = options.textId;
|
|
2714
|
+
this.node = (typeof this.options.node === "string" ? this.manager.nodeManager.get(this.options.node) : this.options.node) ?? this.manager.nodeManager.getLeastUsed();
|
|
2715
|
+
validatePlayerOptions(this.options);
|
|
2716
|
+
this.queue = Structures.Queue(this);
|
|
2717
|
+
this.filterManager = Structures.FilterManager(this);
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2720
|
+
* The lyrics methods for the player.
|
|
2721
|
+
* @type {LyricsMethods}
|
|
2722
|
+
* @readonly
|
|
2723
|
+
*/
|
|
2724
|
+
lyrics = {
|
|
2725
|
+
subscribe: (skipSource) => this.node.lyricsManager.subscribe(this.guildId, skipSource),
|
|
2726
|
+
unsubscribe: () => this.node.lyricsManager.unsubscribe(this.guildId),
|
|
2727
|
+
current: (skipSource) => this.node.lyricsManager.current(this.guildId, skipSource),
|
|
2728
|
+
get: (track, skipSource) => this.node.lyricsManager.get(track, skipSource)
|
|
2729
|
+
};
|
|
2730
|
+
/**
|
|
2731
|
+
*
|
|
2732
|
+
* Search for a track or playlist.
|
|
2733
|
+
* @param {SearchOptions} options The options for the search.
|
|
2734
|
+
* @returns {Promise<QueryResult>} The search result.
|
|
2735
|
+
* @example
|
|
2736
|
+
* ```ts
|
|
2737
|
+
* const player = manager.getPlayer("guildId");
|
|
2738
|
+
* const result = await player.search({
|
|
2739
|
+
* query: "track name",
|
|
2740
|
+
* engine: SearchEngine.Youtube,
|
|
2741
|
+
* requester: {},
|
|
2742
|
+
* });
|
|
2743
|
+
*
|
|
2744
|
+
* console.log(result) // the search result
|
|
2745
|
+
* ```
|
|
2746
|
+
*/
|
|
2747
|
+
search(options) {
|
|
2748
|
+
return this.manager.search({
|
|
2749
|
+
...options,
|
|
2750
|
+
node: this.node
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
*
|
|
2755
|
+
* Play the next track in the queue.
|
|
2756
|
+
* @param {number} [to=0] The amount of tracks to skip.
|
|
2757
|
+
* @param {boolean} [throwError=true] Whether to throw an error if there are no tracks to skip.
|
|
2758
|
+
* @returns {Promise<void>}
|
|
2759
|
+
* @throws {PlayerError} If there are no tracks to skip.
|
|
2760
|
+
* @example
|
|
2761
|
+
* ```ts
|
|
2762
|
+
* const player = manager.getPlayer("guildId");
|
|
2763
|
+
* player.skip(2); // skip 2 tracks
|
|
2764
|
+
* player.skip(); // skip 1 track
|
|
2765
|
+
* ```
|
|
2766
|
+
*/
|
|
2767
|
+
async skip(to = 0, throwError = true) {
|
|
2768
|
+
if (!this.queue.size) {
|
|
2769
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Skip] No tracks to skip.");
|
|
2770
|
+
if (throwError) throw new PlayerError("No tracks to skip.");
|
|
2771
|
+
}
|
|
2772
|
+
if (typeof to === "number" && to > 0) {
|
|
2773
|
+
if (to > this.queue.size) throw new PlayerError("Cannot skip to a track that doesn't exist.");
|
|
2774
|
+
if (to < 0) throw new PlayerError("Cannot skip to a negative number.");
|
|
2775
|
+
this.queue.splice(0, to - 1);
|
|
2776
|
+
}
|
|
2777
|
+
if (!this.playing && !this.queue.current) return this.play();
|
|
2778
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Skip] Skipping to next track for guild: ${this.guildId}`);
|
|
2779
|
+
await this.node.stopPlayer(this.guildId);
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
*
|
|
2783
|
+
* Seek to a specific position in the current track.
|
|
2784
|
+
* @param {number} position The position to seek to in milliseconds.
|
|
2785
|
+
* @returns {Promise<void>}
|
|
2786
|
+
* @throws {PlayerError} If the position is invalid.
|
|
2787
|
+
* @example
|
|
2788
|
+
* ```ts
|
|
2789
|
+
* const player = manager.getPlayer("guildId");
|
|
2790
|
+
* player.seek(30000); // seek to 30 seconds
|
|
2791
|
+
* ```
|
|
2792
|
+
*/
|
|
2793
|
+
async seek(position) {
|
|
2794
|
+
if (typeof position !== "number" || Number.isNaN(position) || position < 0)
|
|
2795
|
+
throw new PlayerError("Position must be a positive number.");
|
|
2796
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Seek] Seeking to ${position} for guild: ${this.guildId}`);
|
|
2797
|
+
await this.updatePlayer({ playerOptions: { position } });
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
*
|
|
2801
|
+
* Disconnect the player from the voice channel.
|
|
2802
|
+
* @returns {Promise<this>} The player instance.
|
|
2803
|
+
* @example
|
|
2804
|
+
* ```ts
|
|
2805
|
+
* const player = manager.getPlayer("guildId");
|
|
2806
|
+
* player.disconnect();
|
|
2807
|
+
* ```
|
|
2808
|
+
*/
|
|
2809
|
+
async disconnect() {
|
|
2810
|
+
if (!this.voiceId) return this;
|
|
2811
|
+
await this.manager.options.sendPayload(this.guildId, {
|
|
2812
|
+
op: 4,
|
|
2813
|
+
d: {
|
|
2814
|
+
guild_id: this.guildId,
|
|
2815
|
+
channel_id: null,
|
|
2816
|
+
self_deaf: this.selfDeaf,
|
|
2817
|
+
self_mute: this.selfMute
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Disconnect] Player disconnected for guild: ${this.guildId}`);
|
|
2821
|
+
this.connected = false;
|
|
2822
|
+
return this;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
*
|
|
2826
|
+
* Destroy and disconnect the player.
|
|
2827
|
+
* @param {DestroyReasons} [reason] The reason for destroying the player.
|
|
2828
|
+
* @returns {Promise<void>}
|
|
2829
|
+
* @example
|
|
2830
|
+
* ```ts
|
|
2831
|
+
* const player = manager.getPlayer("guildId");
|
|
2832
|
+
* player.destroy(DestroyReasons.Stop);
|
|
2833
|
+
* ```
|
|
2834
|
+
*/
|
|
2835
|
+
async destroy(reason = "Player-Stop" /* Stop */) {
|
|
2836
|
+
await this.disconnect();
|
|
2837
|
+
await this.node.destroyPlayer(this.guildId);
|
|
2838
|
+
this.manager.emit("playerDestroy" /* PlayerDestroy */, this, reason);
|
|
2839
|
+
this.manager.emit(
|
|
2840
|
+
"debug" /* Debug */,
|
|
2841
|
+
3 /* Player */,
|
|
2842
|
+
`[Player] -> [Destroy] Destroyed player for guild: ${this.guildId} | Reason: ${reason}`
|
|
2843
|
+
);
|
|
2844
|
+
return this.manager.deletePlayer(this.guildId);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
*
|
|
2848
|
+
* Play a track in the player.
|
|
2849
|
+
* @param {Partial<PlayOptions>} [options] The options to play the track.
|
|
2850
|
+
* @returns {Promise<void>}
|
|
2851
|
+
* @throws {PlayerError} If there are no tracks to play.
|
|
2852
|
+
* @example
|
|
2853
|
+
* ```ts
|
|
2854
|
+
* const player = manager.getPlayer("guildId");
|
|
2855
|
+
*
|
|
2856
|
+
* player.play({
|
|
2857
|
+
* track: track,
|
|
2858
|
+
* noReplace: true,
|
|
2859
|
+
* });
|
|
2860
|
+
* ```
|
|
2861
|
+
*/
|
|
2862
|
+
async play(options = {}) {
|
|
2863
|
+
if (typeof options !== "object") throw new PlayerError("The play options must be an object.");
|
|
2864
|
+
if (options.track) this.queue.current = await validateTrack(this, options.track);
|
|
2865
|
+
else if (!this.queue.current) this.queue.current = await validateTrack(this, this.queue.shift());
|
|
2866
|
+
if (!this.queue.current) throw new PlayerError("No track to play.");
|
|
2867
|
+
if (!isTrack(this.queue.current) && !isUnresolvedTrack(this.queue.current))
|
|
2868
|
+
throw new PlayerError("The track must be a valid Track or UnresolvedTrack instance.");
|
|
2869
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Play] A new track is playing: ${this.queue.current.info.title}`);
|
|
2870
|
+
await this.updatePlayer({
|
|
2871
|
+
noReplace: options.noReplace,
|
|
2872
|
+
playerOptions: {
|
|
2873
|
+
...options,
|
|
2874
|
+
track: {
|
|
2875
|
+
userData: this.queue.current.userData,
|
|
2876
|
+
encoded: this.queue.current.encoded
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
});
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Connect the player to the voice channel.
|
|
2884
|
+
* @returns {Promise<this>} The player instance.
|
|
2885
|
+
* @example
|
|
2886
|
+
* ```ts
|
|
2887
|
+
* const player = manager.getPlayer("guildId");
|
|
2888
|
+
* player.connect();
|
|
2889
|
+
* ```
|
|
2890
|
+
*/
|
|
2891
|
+
async connect() {
|
|
2892
|
+
if (!this.voiceId) return this;
|
|
2893
|
+
await this.manager.options.sendPayload(this.guildId, {
|
|
2894
|
+
op: 4,
|
|
2895
|
+
d: {
|
|
2896
|
+
guild_id: this.guildId,
|
|
2897
|
+
channel_id: this.voiceId,
|
|
2898
|
+
self_deaf: this.selfDeaf,
|
|
2899
|
+
self_mute: this.selfMute
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Connect] Player connected for guild: ${this.guildId}`);
|
|
2903
|
+
this.connected = true;
|
|
2904
|
+
return this;
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
*
|
|
2908
|
+
* Stop the player from playing.
|
|
2909
|
+
* @param {boolean} [destroy=true] Whether to destroy the player or not.
|
|
2910
|
+
* @returns {Promise<void>}
|
|
2911
|
+
* @example
|
|
2912
|
+
* ```ts
|
|
2913
|
+
* const player = manager.getPlayer("guildId");
|
|
2914
|
+
* player.stop();
|
|
2915
|
+
* ```
|
|
2916
|
+
*/
|
|
2917
|
+
async stop(destroy = true) {
|
|
2918
|
+
await this.node.stopPlayer(this.guildId);
|
|
2919
|
+
if (destroy) await this.destroy("Player-Stop" /* Stop */);
|
|
2920
|
+
this.manager.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Stop] Player stopped for guild: ${this.guildId}`);
|
|
2921
|
+
this.playing = false;
|
|
2922
|
+
this.paused = false;
|
|
2923
|
+
this.position = 0;
|
|
2924
|
+
this.queue.current = null;
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
*
|
|
2929
|
+
* Pause or resume the player.
|
|
2930
|
+
* @returns {Promise<void>}
|
|
2931
|
+
* @example
|
|
2932
|
+
* ```ts
|
|
2933
|
+
* const player = manager.getPlayer("guildId");
|
|
2934
|
+
* player.setPaused();
|
|
2935
|
+
* ```
|
|
2936
|
+
*/
|
|
2937
|
+
async setPaused(paused = !this.paused) {
|
|
2938
|
+
this.manager.emit(
|
|
2939
|
+
"debug" /* Debug */,
|
|
2940
|
+
3 /* Player */,
|
|
2941
|
+
`[Player] -> [Pause] Player is now ${paused ? "paused" : "resumed"} for guild: ${this.guildId}`
|
|
2942
|
+
);
|
|
2943
|
+
await this.updatePlayer({ playerOptions: { paused } });
|
|
2944
|
+
return paused;
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
*
|
|
2948
|
+
* Set the volume of the player.
|
|
2949
|
+
* @param {number} volume The volume to set.
|
|
2950
|
+
* @returns {Promise<void>}
|
|
2951
|
+
* @example
|
|
2952
|
+
* ```ts
|
|
2953
|
+
* const player = manager.getPlayer("guildId");
|
|
2954
|
+
* player.setVolume(50); // set the volume to 50%
|
|
2955
|
+
* ```
|
|
2956
|
+
*/
|
|
2957
|
+
async setVolume(volume) {
|
|
2958
|
+
if (typeof volume !== "number" || Number.isNaN(volume) || volume < 0 || volume > 100)
|
|
2959
|
+
throw new PlayerError("Volume must be a number between 0 and 100.");
|
|
2960
|
+
await this.updatePlayer({ playerOptions: { volume } });
|
|
2961
|
+
this.volume = volume;
|
|
2962
|
+
this.manager.emit(
|
|
2963
|
+
"debug" /* Debug */,
|
|
2964
|
+
3 /* Player */,
|
|
2965
|
+
`[Player] -> [Volume] Player volume set to ${volume}% for guild: ${this.guildId}`
|
|
2966
|
+
);
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
*
|
|
2971
|
+
* Set the loop mode of the player.
|
|
2972
|
+
* @param {LoopMode} mode The loop mode to set.
|
|
2973
|
+
* @throws {PlayerError} If the loop mode is invalid.
|
|
2974
|
+
* @example
|
|
2975
|
+
* ```ts
|
|
2976
|
+
* const player = manager.getPlayer("guildId");
|
|
2977
|
+
* player.setLoop(LoopMode.Track);
|
|
2978
|
+
* ```
|
|
2979
|
+
*/
|
|
2980
|
+
setLoop(mode) {
|
|
2981
|
+
const loopValues = Object.values(LoopMode).filter((v) => typeof v === "number");
|
|
2982
|
+
if (!loopValues.includes(mode)) throw new PlayerError(`Invalid loop mode. Valid modes are: ${loopValues.join(", ")}`);
|
|
2983
|
+
this.loop = mode;
|
|
2984
|
+
this.manager.emit(
|
|
2985
|
+
"debug" /* Debug */,
|
|
2986
|
+
3 /* Player */,
|
|
2987
|
+
`[Player] -> [Loop] Player loop mode set to ${mode} for guild: ${this.guildId}`
|
|
2988
|
+
);
|
|
2989
|
+
return this;
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Set the voice of the player.
|
|
2993
|
+
* @param {Partial<VoiceChannelUpdate>} voice The voice state to set.
|
|
2994
|
+
* @returns {Promise<void>}
|
|
2995
|
+
* @example
|
|
2996
|
+
* ```ts
|
|
2997
|
+
* const player = manager.getPlayer("guildId");
|
|
2998
|
+
* player.setVoice({ voiceId: "newVoiceId" });
|
|
2999
|
+
* ```
|
|
3000
|
+
*/
|
|
3001
|
+
async setVoice(voice) {
|
|
3002
|
+
if (voice.voiceId === this.voiceId) return;
|
|
3003
|
+
if (voice.voiceId) {
|
|
3004
|
+
this.voiceId = voice.voiceId;
|
|
3005
|
+
this.options.voiceId = voice.voiceId;
|
|
3006
|
+
}
|
|
3007
|
+
if (voice.selfDeaf) {
|
|
3008
|
+
this.selfDeaf = voice.selfDeaf;
|
|
3009
|
+
this.options.selfDeaf = voice.selfDeaf;
|
|
3010
|
+
}
|
|
3011
|
+
if (voice.selfMute) {
|
|
3012
|
+
this.selfMute = voice.selfMute;
|
|
3013
|
+
this.options.selfMute = voice.selfMute;
|
|
3014
|
+
}
|
|
3015
|
+
await this.manager.options.sendPayload(this.guildId, {
|
|
3016
|
+
op: 4,
|
|
3017
|
+
d: {
|
|
3018
|
+
guild_id: this.guildId,
|
|
3019
|
+
self_deaf: this.selfDeaf,
|
|
3020
|
+
self_mute: this.selfMute,
|
|
3021
|
+
channel_id: this.voiceId ?? null
|
|
3022
|
+
}
|
|
3023
|
+
});
|
|
3024
|
+
this.manager.emit(
|
|
3025
|
+
"debug" /* Debug */,
|
|
3026
|
+
3 /* Player */,
|
|
3027
|
+
`[Player] -> [VoiceState] Updated voice state for guild: ${this.guildId} with voiceId: ${this.voiceId}`
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
*
|
|
3032
|
+
* Change the node the player is connected to.
|
|
3033
|
+
* @param {NodeIdentifier} node The node to change to.
|
|
3034
|
+
* @returns {Promise<void>} A promise that resolves when the node has been changed.
|
|
3035
|
+
* @throws {PlayerError} If the target node is not found, not connected, or missing source managers.
|
|
3036
|
+
* @example
|
|
3037
|
+
* ```ts
|
|
3038
|
+
* const player = manager.getPlayer("guildId");
|
|
3039
|
+
* player.move("newNodeId");
|
|
3040
|
+
* ```
|
|
3041
|
+
*/
|
|
3042
|
+
async move(node) {
|
|
3043
|
+
const id = typeof node === "string" ? node : node.id;
|
|
3044
|
+
const target = this.manager.nodeManager.get(id);
|
|
3045
|
+
if (!target) throw new PlayerError("Target node not found.");
|
|
3046
|
+
if (!target.info) throw new PlayerError("Target node info not available.");
|
|
3047
|
+
if (target.state !== 2 /* Connected */) throw new PlayerError("Target node is not connected.");
|
|
3048
|
+
if (target.id === this.node.id) return;
|
|
3049
|
+
if (this.queue.current || this.queue.size) {
|
|
3050
|
+
const sources = [this.queue.current, ...this.queue.tracks].filter((t) => t != null || typeof t !== "undefined").map((t) => t.info.sourceName).filter((s) => s != null || typeof s !== "undefined");
|
|
3051
|
+
const missings = [...new Set(sources)].filter((s) => !target.info.sourceManagers.includes(s));
|
|
3052
|
+
if (missings.length) throw new PlayerError(`Target node is missing source managers for: ${missings.join(", ")}`);
|
|
3053
|
+
}
|
|
3054
|
+
const current = this.queue.current;
|
|
3055
|
+
if (!this.voice.endpoint || !this.voice.sessionId || !this.voice.token)
|
|
3056
|
+
throw new PlayerError("Player voice connection data is incomplete.");
|
|
3057
|
+
if (this.node.state === 2 /* Connected */) await this.node.destroyPlayer(this.guildId);
|
|
3058
|
+
this.node = target;
|
|
3059
|
+
await this.updatePlayer({
|
|
3060
|
+
playerOptions: {
|
|
3061
|
+
voice: this.voice,
|
|
3062
|
+
...current && {
|
|
3063
|
+
track: current.encoded,
|
|
3064
|
+
position: this.position,
|
|
3065
|
+
volume: this.volume
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
await this.filterManager.apply();
|
|
3070
|
+
this.manager.emit(
|
|
3071
|
+
"debug" /* Debug */,
|
|
3072
|
+
3 /* Player */,
|
|
3073
|
+
`[Player] -> [Move] Player moved to node: ${target.id} for guild: ${this.guildId}`
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Update the player with new data.
|
|
3078
|
+
* @param {NonGuildUpdatePlayerInfo} data The data to update the player with.
|
|
3079
|
+
* @returns {Promise<LavalinkPlayer | null>} The updated player data.
|
|
3080
|
+
*/
|
|
3081
|
+
async updatePlayer(data) {
|
|
3082
|
+
return this.node.updatePlayer({
|
|
3083
|
+
guildId: this.guildId,
|
|
3084
|
+
...data
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
/**
|
|
3088
|
+
*
|
|
3089
|
+
* Return the player as a json object.
|
|
3090
|
+
* @returns {PlayerJson}
|
|
3091
|
+
* @example
|
|
3092
|
+
* ```ts
|
|
3093
|
+
* const player = manager.getPlayer("guildId");
|
|
3094
|
+
* const json = player.toJSON();
|
|
3095
|
+
* console.log(json); // the player as a json object
|
|
3096
|
+
* ```
|
|
3097
|
+
*/
|
|
3098
|
+
toJSON() {
|
|
3099
|
+
return {
|
|
3100
|
+
volume: this.volume,
|
|
3101
|
+
loop: this.loop,
|
|
3102
|
+
paused: this.paused,
|
|
3103
|
+
playing: this.playing,
|
|
3104
|
+
voiceId: this.voiceId,
|
|
3105
|
+
guildId: this.guildId,
|
|
3106
|
+
selfMute: this.selfMute,
|
|
3107
|
+
selfDeaf: this.selfDeaf,
|
|
3108
|
+
options: this.options,
|
|
3109
|
+
voice: this.voice,
|
|
3110
|
+
queue: this.queue.toJSON(),
|
|
3111
|
+
node: this.node.toJSON()
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/classes/queue/Store.ts
|
|
3117
|
+
var QueueStore = class {
|
|
3118
|
+
/**
|
|
3119
|
+
* Storage manager instance.
|
|
3120
|
+
* @type {StorageAdapter}
|
|
3121
|
+
* @private
|
|
3122
|
+
* @readonly
|
|
3123
|
+
* @internal
|
|
3124
|
+
*/
|
|
3125
|
+
storage;
|
|
3126
|
+
/**
|
|
3127
|
+
*
|
|
3128
|
+
* Constructor of the queue store.
|
|
3129
|
+
* @param {StorageAdapter} storage Storage manager instance.
|
|
3130
|
+
* @example
|
|
3131
|
+
* ```ts
|
|
3132
|
+
* const storage = new CustomStorage();
|
|
3133
|
+
* const queueStore = new QueueStore(storage);
|
|
3134
|
+
*
|
|
3135
|
+
* console.log(queueStore);
|
|
3136
|
+
* ```
|
|
3137
|
+
*/
|
|
3138
|
+
constructor(storage) {
|
|
3139
|
+
this.storage = storage;
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
*
|
|
3143
|
+
* Get the value using the key.
|
|
3144
|
+
* @param {string} key The key to get the value from.
|
|
3145
|
+
* @returns <Awaitable<QueueJson | undefined>> The value of the key.
|
|
3146
|
+
* @example
|
|
3147
|
+
* ```ts
|
|
3148
|
+
* const value = await queueStore.get("key");
|
|
3149
|
+
* console.log(value); // { id: "key", data: "value" }
|
|
3150
|
+
* ```
|
|
3151
|
+
*/
|
|
3152
|
+
get(key) {
|
|
3153
|
+
return this.storage.get(key);
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
*
|
|
3157
|
+
* Set the value using the key.
|
|
3158
|
+
* @param {string} key The key to set the value to.
|
|
3159
|
+
* @param {unknown} value The value to set.
|
|
3160
|
+
* @returns <Awaitable<void>> Ganyu is the best waifu.
|
|
3161
|
+
* @example
|
|
3162
|
+
* ```ts
|
|
3163
|
+
* await queueStore.set("key", { id: "key", data: "value" });
|
|
3164
|
+
* ```
|
|
3165
|
+
*/
|
|
3166
|
+
set(key, value) {
|
|
3167
|
+
return this.storage.set(key, value);
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
*
|
|
3171
|
+
* Delete the value using the key.
|
|
3172
|
+
* @param {string} key The key to delete the value from.
|
|
3173
|
+
* @returns <Awaitable<boolean>> Returns true if the key was deleted.
|
|
3174
|
+
* @example
|
|
3175
|
+
* ```ts
|
|
3176
|
+
* const success = await queueStore.delete("key");
|
|
3177
|
+
* console.log(success); // true
|
|
3178
|
+
* ```
|
|
3179
|
+
*/
|
|
3180
|
+
delete(key) {
|
|
3181
|
+
return this.storage.delete(key);
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3184
|
+
|
|
3185
|
+
// src/classes/queue/Utils.ts
|
|
3186
|
+
var QueueUtils = class {
|
|
3187
|
+
/**
|
|
3188
|
+
* Player instance.
|
|
3189
|
+
* @type {Queue}
|
|
3190
|
+
* @private
|
|
3191
|
+
* @readonly
|
|
3192
|
+
* @internal
|
|
3193
|
+
*/
|
|
3194
|
+
queue;
|
|
3195
|
+
/**
|
|
3196
|
+
* Queue store.
|
|
3197
|
+
* @type {QueueStore}
|
|
3198
|
+
* @private
|
|
3199
|
+
* @readonly
|
|
3200
|
+
* @internal
|
|
3201
|
+
*/
|
|
3202
|
+
store;
|
|
3203
|
+
/**
|
|
3204
|
+
* Options for the queue.
|
|
3205
|
+
* @type {HoshimiQueueOptions}
|
|
3206
|
+
* @private
|
|
3207
|
+
* @readonly
|
|
3208
|
+
* @internal
|
|
3209
|
+
*/
|
|
3210
|
+
options;
|
|
3211
|
+
/**
|
|
3212
|
+
*
|
|
3213
|
+
* Constructor of the queue utils.
|
|
3214
|
+
* @param {QueueStructure} queue The queue instance.
|
|
3215
|
+
*/
|
|
3216
|
+
constructor(queue) {
|
|
3217
|
+
this.queue = queue;
|
|
3218
|
+
this.options = queue.player.manager.options.queueOptions;
|
|
3219
|
+
this.store = new QueueStore(this.options.storage);
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
*
|
|
3223
|
+
* Save the queue.
|
|
3224
|
+
* @returns {Awaitable<void>}
|
|
3225
|
+
* @example
|
|
3226
|
+
* ```ts
|
|
3227
|
+
* await player.queue.utils.save();
|
|
3228
|
+
* ```
|
|
3229
|
+
*/
|
|
3230
|
+
save() {
|
|
3231
|
+
const max = this.options.maxPreviousTracks;
|
|
3232
|
+
const length = this.queue.tracks.length;
|
|
3233
|
+
const json = this.queue.toJSON();
|
|
3234
|
+
if (length > max) this.queue.history.splice(0, length - max);
|
|
3235
|
+
this.queue.player.manager.emit(
|
|
3236
|
+
"debug" /* Debug */,
|
|
3237
|
+
5 /* Queue */,
|
|
3238
|
+
`[Queue] -> [Adapter] Saving queue for ${this.queue.player.guildId} | Object: ${JSON.stringify(json)}`
|
|
3239
|
+
);
|
|
3240
|
+
return this.store.set(this.queue.player.guildId, json);
|
|
3241
|
+
}
|
|
3242
|
+
/**
|
|
3243
|
+
*
|
|
3244
|
+
* Destroy the queue.
|
|
3245
|
+
* @returns {Promise<void>}
|
|
3246
|
+
* @example
|
|
3247
|
+
* ```ts
|
|
3248
|
+
* await player.queue.utils.destroy();
|
|
3249
|
+
* ```
|
|
3250
|
+
*/
|
|
3251
|
+
destroy() {
|
|
3252
|
+
this.queue.player.manager.emit(
|
|
3253
|
+
"debug" /* Debug */,
|
|
3254
|
+
5 /* Queue */,
|
|
3255
|
+
`[Queue] -> [Adapter] Destroying queue for ${this.queue.player.guildId}`
|
|
3256
|
+
);
|
|
3257
|
+
return this.store.delete(this.queue.player.guildId);
|
|
3258
|
+
}
|
|
3259
|
+
/**
|
|
3260
|
+
*
|
|
3261
|
+
* Sync the queue.
|
|
3262
|
+
* @returns {Awaitable<void>}
|
|
3263
|
+
* @example
|
|
3264
|
+
* ```ts
|
|
3265
|
+
* await player.queue.utils.sync();
|
|
3266
|
+
* ```
|
|
3267
|
+
*/
|
|
3268
|
+
async sync(override = true, syncCurrent = false) {
|
|
3269
|
+
const data = await this.store.get(this.queue.player.guildId);
|
|
3270
|
+
if (!data) throw new StorageError(`No data found to sync for guildId: ${this.queue.player.guildId}`);
|
|
3271
|
+
if (syncCurrent && data.current && !this.queue.current && isTrack(data.current)) this.queue.current = data.current;
|
|
3272
|
+
const tracks = data.tracks.filter((track) => isTrack(track)) || [];
|
|
3273
|
+
const history = data.history.filter((track) => isTrack(track)) || [];
|
|
3274
|
+
const length = this.queue.tracks.length;
|
|
3275
|
+
if (tracks.length) this.queue.tracks.splice(override ? 0 : length, override ? length : 0, ...tracks);
|
|
3276
|
+
if (history.length) this.queue.history.splice(0, override ? length : 0, ...history);
|
|
3277
|
+
this.queue.player.manager.emit(
|
|
3278
|
+
"debug" /* Debug */,
|
|
3279
|
+
5 /* Queue */,
|
|
3280
|
+
`[Queue] -> [Adapter] Syncing queue for ${this.queue.player.guildId} | Object: ${JSON.stringify(data)}`
|
|
3281
|
+
);
|
|
3282
|
+
await this.save();
|
|
3283
|
+
}
|
|
3284
|
+
};
|
|
3285
|
+
|
|
3286
|
+
// src/classes/queue/Queue.ts
|
|
3287
|
+
var Queue = class {
|
|
3288
|
+
/**
|
|
3289
|
+
* Tracks of the queue.
|
|
3290
|
+
* @type {HoshimiTrack[]}
|
|
3291
|
+
*/
|
|
3292
|
+
tracks = [];
|
|
3293
|
+
/**
|
|
3294
|
+
* Previous tracks of the queue.
|
|
3295
|
+
* @type {Track[]}
|
|
3296
|
+
*/
|
|
3297
|
+
history = [];
|
|
3298
|
+
/**
|
|
3299
|
+
* Current track of the queue.
|
|
3300
|
+
* @type {Track | null}
|
|
3301
|
+
*/
|
|
3302
|
+
current = null;
|
|
3303
|
+
/**
|
|
3304
|
+
* The player instance.
|
|
3305
|
+
* @type {PlayerStructure}
|
|
3306
|
+
*/
|
|
3307
|
+
player;
|
|
3308
|
+
/**
|
|
3309
|
+
* The queue utils instance.
|
|
3310
|
+
* @type {QueueUtils}
|
|
3311
|
+
* @readonly
|
|
3312
|
+
*/
|
|
3313
|
+
utils;
|
|
3314
|
+
/**
|
|
3315
|
+
*
|
|
3316
|
+
* Constructor of the queue.
|
|
3317
|
+
* @param {PlayerStructure} player Player instance.
|
|
3318
|
+
* @example
|
|
3319
|
+
* ```ts
|
|
3320
|
+
* const player = new Player();
|
|
3321
|
+
* const queue = new Queue(player);
|
|
3322
|
+
*
|
|
3323
|
+
* console.log(queue.size); // 0
|
|
3324
|
+
* queue.add(track);
|
|
3325
|
+
* console.log(queue.size); // 1
|
|
3326
|
+
* ```
|
|
3327
|
+
*/
|
|
3328
|
+
constructor(player) {
|
|
3329
|
+
this.player = player;
|
|
3330
|
+
this.utils = new QueueUtils(this);
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Get the track size of the queue.
|
|
3334
|
+
* @type {number}
|
|
3335
|
+
* @returns {number} The track size of the queue.
|
|
3336
|
+
* @readonly
|
|
3337
|
+
* @example
|
|
3338
|
+
* ```ts
|
|
3339
|
+
* const queue = player.queue;
|
|
3340
|
+
*
|
|
3341
|
+
* console.log(queue.size); // 0
|
|
3342
|
+
* queue.add(track);
|
|
3343
|
+
*
|
|
3344
|
+
* console.log(queue.size); // 1
|
|
3345
|
+
* queue.add([track1, track2]);
|
|
3346
|
+
*
|
|
3347
|
+
* console.log(queue.size); // 3
|
|
3348
|
+
* queue.shift();
|
|
3349
|
+
* console.log(queue.size); // 2
|
|
3350
|
+
*
|
|
3351
|
+
* queue.clear();
|
|
3352
|
+
* console.log(queue.size); // 0
|
|
3353
|
+
* ```
|
|
3354
|
+
*/
|
|
3355
|
+
get size() {
|
|
3356
|
+
return this.tracks.length;
|
|
3357
|
+
}
|
|
3358
|
+
/**
|
|
3359
|
+
* Get the total track size of the queue (Includes the current track).
|
|
3360
|
+
* @type {number}
|
|
3361
|
+
* @returns {number} The total track size of the queue.
|
|
3362
|
+
* @readonly
|
|
3363
|
+
* @example
|
|
3364
|
+
* ```ts
|
|
3365
|
+
* const queue = player.queue;
|
|
3366
|
+
*
|
|
3367
|
+
* console.log(queue.totalSize); // 0
|
|
3368
|
+
* queue.add(track);
|
|
3369
|
+
*
|
|
3370
|
+
* console.log(queue.totalSize); // 1
|
|
3371
|
+
* queue.add([track1, track2]);
|
|
3372
|
+
*
|
|
3373
|
+
* console.log(queue.totalSize); // 3
|
|
3374
|
+
* queue.shift();
|
|
3375
|
+
* console.log(queue.totalSize); // 2
|
|
3376
|
+
*
|
|
3377
|
+
* queue.clear();
|
|
3378
|
+
* console.log(queue.totalSize); // 0
|
|
3379
|
+
* ```
|
|
3380
|
+
*/
|
|
3381
|
+
get totalSize() {
|
|
3382
|
+
return this.size + Number(!!this.current);
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
*
|
|
3386
|
+
* Check if the queue is empty.
|
|
3387
|
+
* @type {boolean}
|
|
3388
|
+
* @returns {boolean} True if the queue is empty.
|
|
3389
|
+
* @readonly
|
|
3390
|
+
* @example
|
|
3391
|
+
* ```ts
|
|
3392
|
+
* const queue = player.queue;
|
|
3393
|
+
*
|
|
3394
|
+
* console.log(queue.isEmpty()); // true
|
|
3395
|
+
* queue.add(track);
|
|
3396
|
+
*
|
|
3397
|
+
* console.log(queue.isEmpty()); // false
|
|
3398
|
+
* queue.clear();
|
|
3399
|
+
*
|
|
3400
|
+
* console.log(queue.isEmpty()); // true
|
|
3401
|
+
* ```
|
|
3402
|
+
*/
|
|
3403
|
+
isEmpty() {
|
|
3404
|
+
return this.size === 0;
|
|
3405
|
+
}
|
|
3406
|
+
/**
|
|
3407
|
+
*
|
|
3408
|
+
* Get the previous track of the queue.
|
|
3409
|
+
* @param {boolean} [remove=false] Whether to remove the track from the previous queue.
|
|
3410
|
+
* @returns {Track | null} The previous track of the queue.
|
|
3411
|
+
* @example
|
|
3412
|
+
* ```ts
|
|
3413
|
+
* const queue = player.queue;
|
|
3414
|
+
*
|
|
3415
|
+
* console.log(queue.previous()); // null
|
|
3416
|
+
* queue.add(track);
|
|
3417
|
+
* queue.add(track2);
|
|
3418
|
+
*
|
|
3419
|
+
* console.log(queue.previous()); // track
|
|
3420
|
+
* console.log(queue.previous(true)); // track and remove it from the previous tracks
|
|
3421
|
+
* ```
|
|
3422
|
+
*/
|
|
3423
|
+
previous(remove = false) {
|
|
3424
|
+
if (remove) return this.history.shift() ?? null;
|
|
3425
|
+
return this.history[0] ?? null;
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
*
|
|
3429
|
+
* Add a track or tracks to the queue.
|
|
3430
|
+
* @param {Track | Track[]} track The track or tracks to add.
|
|
3431
|
+
* @param {number} [position] The position to add the track or tracks.
|
|
3432
|
+
* @returns {this} The queue instance.
|
|
3433
|
+
* @example
|
|
3434
|
+
* ```ts
|
|
3435
|
+
* const queue = player.queue;
|
|
3436
|
+
*
|
|
3437
|
+
* console.log(queue.size); // 0
|
|
3438
|
+
*
|
|
3439
|
+
* queue.add(track);
|
|
3440
|
+
* console.log(queue.size); // 1
|
|
3441
|
+
*
|
|
3442
|
+
* queue.add([track1, track2]);
|
|
3443
|
+
* console.log(queue.size); // 3
|
|
3444
|
+
*
|
|
3445
|
+
* queue.add(track3, 1);
|
|
3446
|
+
* console.log(queue.size); // 4
|
|
3447
|
+
* console.log(queue.tracks); // [track1, track3, track2, track]
|
|
3448
|
+
* ```
|
|
3449
|
+
*/
|
|
3450
|
+
add(track, position) {
|
|
3451
|
+
const tracks = Array.isArray(track) ? track : [track];
|
|
3452
|
+
if (typeof position === "number" && position >= 0 && position < this.tracks.length) return this.splice(position, 0, ...tracks);
|
|
3453
|
+
this.tracks.push(...tracks);
|
|
3454
|
+
this.player.manager.emit("queueUpdate" /* QueueUpdate */, this.player, this);
|
|
3455
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, `[Queue] -> [Add] Added ${this.tracks.length} tracks to the queue.`);
|
|
3456
|
+
return this;
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
*
|
|
3460
|
+
* Get the first track of the queue.
|
|
3461
|
+
* @returns {HoshimiTrack | null} The first track of the queue.
|
|
3462
|
+
* @example
|
|
3463
|
+
* ```ts
|
|
3464
|
+
* const queue = player.queue;
|
|
3465
|
+
*
|
|
3466
|
+
* console.log(queue.shift()); // null
|
|
3467
|
+
* queue.add(track);
|
|
3468
|
+
*
|
|
3469
|
+
* console.log(queue.shift()); // track
|
|
3470
|
+
* queue.add(track2);
|
|
3471
|
+
* ```
|
|
3472
|
+
*/
|
|
3473
|
+
shift() {
|
|
3474
|
+
return this.tracks.shift() ?? null;
|
|
3475
|
+
}
|
|
3476
|
+
/**
|
|
3477
|
+
*
|
|
3478
|
+
* Add tracks to the beginning of the queue.
|
|
3479
|
+
* @param {Track[]} tracks The tracks to add.
|
|
3480
|
+
* @returns {this} The queue instance.
|
|
3481
|
+
* @example
|
|
3482
|
+
* ```ts
|
|
3483
|
+
* const queue = player.queue;
|
|
3484
|
+
*
|
|
3485
|
+
* console.log(queue.size); // 0
|
|
3486
|
+
* queue.unshift(track);
|
|
3487
|
+
*
|
|
3488
|
+
* console.log(queue.size); // 1
|
|
3489
|
+
* queue.unshift([track1, track2]);
|
|
3490
|
+
*
|
|
3491
|
+
* console.log(queue.size); // 3
|
|
3492
|
+
* console.log(queue.tracks); // [track1, track2, track]
|
|
3493
|
+
* ```
|
|
3494
|
+
*/
|
|
3495
|
+
unshift(...tracks) {
|
|
3496
|
+
this.tracks.unshift(...tracks);
|
|
3497
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, `[Queue] -> [Unshift] Added ${this.tracks.length} tracks to the queue.`);
|
|
3498
|
+
return this;
|
|
3499
|
+
}
|
|
3500
|
+
/**
|
|
3501
|
+
*
|
|
3502
|
+
* Shuffle the queue.
|
|
3503
|
+
* @returns {this} The queue instance.
|
|
3504
|
+
* @example
|
|
3505
|
+
* ```ts
|
|
3506
|
+
* const queue = player.queue;
|
|
3507
|
+
*
|
|
3508
|
+
* console.log(queue.size); // 0
|
|
3509
|
+
* queue.add(track);
|
|
3510
|
+
* queue.add([track1, track2]);
|
|
3511
|
+
*
|
|
3512
|
+
* console.log(queue.size); // 3
|
|
3513
|
+
* console.log(queue.tracks); // [track, track1, track2]
|
|
3514
|
+
*
|
|
3515
|
+
* queue.shuffle();
|
|
3516
|
+
* console.log(queue.tracks); // [track2, track, track1]
|
|
3517
|
+
* ```
|
|
3518
|
+
*/
|
|
3519
|
+
shuffle() {
|
|
3520
|
+
if (this.size <= 1) return this;
|
|
3521
|
+
if (this.size === 2) [this.tracks[0], this.tracks[1]] = [this.tracks[1], this.tracks[0]];
|
|
3522
|
+
else {
|
|
3523
|
+
for (let i = this.tracks.length - 1; i > 0; i--) {
|
|
3524
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3525
|
+
[this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
this.player.manager.emit("queueUpdate" /* QueueUpdate */, this.player, this);
|
|
3529
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, "[Queue] -> [Shuffle] Shuffled the queue.");
|
|
3530
|
+
return this;
|
|
3531
|
+
}
|
|
3532
|
+
/**
|
|
3533
|
+
*
|
|
3534
|
+
* Clear the queue.
|
|
3535
|
+
* @returns {this} The queue instance.
|
|
3536
|
+
* @example
|
|
3537
|
+
* ```ts
|
|
3538
|
+
* const queue = player.queue;
|
|
3539
|
+
*
|
|
3540
|
+
* console.log(queue.size); // 0
|
|
3541
|
+
* queue.add(track);
|
|
3542
|
+
* queue.add([track1, track2]);
|
|
3543
|
+
*
|
|
3544
|
+
* console.log(queue.size); // 3
|
|
3545
|
+
* queue.clear();
|
|
3546
|
+
* console.log(queue.size); // 0
|
|
3547
|
+
* ```
|
|
3548
|
+
*/
|
|
3549
|
+
clear() {
|
|
3550
|
+
this.tracks = [];
|
|
3551
|
+
this.history = [];
|
|
3552
|
+
this.current = null;
|
|
3553
|
+
this.player.manager.emit("queueUpdate" /* QueueUpdate */, this.player, this);
|
|
3554
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, "[Queue] -> [Clear] Cleared the queue.");
|
|
3555
|
+
return this;
|
|
3556
|
+
}
|
|
3557
|
+
/**
|
|
3558
|
+
*
|
|
3559
|
+
* Move a track to a specific position in the queue.
|
|
3560
|
+
* @param {Track} track The track to move.
|
|
3561
|
+
* @param {number} to The position to move.
|
|
3562
|
+
* @returns {this} The queue instance.
|
|
3563
|
+
*/
|
|
3564
|
+
move(track, to) {
|
|
3565
|
+
const index = this.tracks.indexOf(track);
|
|
3566
|
+
if (index === -1) return this;
|
|
3567
|
+
this.splice(index, 1);
|
|
3568
|
+
this.add(track, to - 1);
|
|
3569
|
+
this.player.manager.emit("queueUpdate" /* QueueUpdate */, this.player, this);
|
|
3570
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, `[Queue] -> [Move] Moved track ${track.info.title} to position ${to}.`);
|
|
3571
|
+
return this;
|
|
3572
|
+
}
|
|
3573
|
+
/**
|
|
3574
|
+
*
|
|
3575
|
+
* Delete tracks from the queue.
|
|
3576
|
+
* @param {number} start The start index.
|
|
3577
|
+
* @param {number} deleteCount The number of tracks to delete.
|
|
3578
|
+
* @param {Track | Track[]} [tracks] The tracks to add.
|
|
3579
|
+
* @returns {this} The queue instance.
|
|
3580
|
+
*/
|
|
3581
|
+
splice(start, deleteCount, tracks) {
|
|
3582
|
+
if (!this.size && tracks) this.add(tracks);
|
|
3583
|
+
if (tracks) this.tracks.splice(start, deleteCount, ...Array.isArray(tracks) ? tracks : [tracks]);
|
|
3584
|
+
else this.tracks.splice(start, deleteCount);
|
|
3585
|
+
this.player.manager.emit("queueUpdate" /* QueueUpdate */, this.player, this);
|
|
3586
|
+
this.player.manager.emit("debug" /* Debug */, 5 /* Queue */, `[Queue] -> [Splice] Removed ${deleteCount} tracks from the queue.`);
|
|
3587
|
+
return this;
|
|
3588
|
+
}
|
|
3589
|
+
/**
|
|
3590
|
+
*
|
|
3591
|
+
* Convert the queue to a JSON object.
|
|
3592
|
+
* @returns {QueueJson} The queue JSON object.
|
|
3593
|
+
*/
|
|
3594
|
+
toJSON() {
|
|
3595
|
+
const max = this.player.manager.options.queueOptions.maxPreviousTracks;
|
|
3596
|
+
if (this.history.length > max) this.history.splice(max, this.history.length);
|
|
3597
|
+
return {
|
|
3598
|
+
tracks: this.tracks,
|
|
3599
|
+
history: this.history,
|
|
3600
|
+
current: this.current
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
|
|
3605
|
+
// src/types/Structures.ts
|
|
3606
|
+
var Structures = {
|
|
3607
|
+
Player(...args) {
|
|
3608
|
+
return new Player(...args);
|
|
3609
|
+
},
|
|
3610
|
+
Rest(...args) {
|
|
3611
|
+
return new Rest(...args);
|
|
3612
|
+
},
|
|
3613
|
+
Node(...args) {
|
|
3614
|
+
return new Node(...args);
|
|
3615
|
+
},
|
|
3616
|
+
Queue(...args) {
|
|
3617
|
+
return new Queue(...args);
|
|
3618
|
+
},
|
|
3619
|
+
LyricsManager(...args) {
|
|
3620
|
+
return new LyricsManager(...args);
|
|
3621
|
+
},
|
|
3622
|
+
NodeManager(...args) {
|
|
3623
|
+
return new NodeManager(...args);
|
|
3624
|
+
},
|
|
3625
|
+
FilterManager(...args) {
|
|
3626
|
+
return new FilterManager(...args);
|
|
3627
|
+
}
|
|
3628
|
+
};
|
|
3629
|
+
|
|
3630
|
+
// src/classes/node/Node.ts
|
|
3631
|
+
var Node = class {
|
|
3632
|
+
/**
|
|
3633
|
+
* The options for the node.
|
|
3634
|
+
* @type {Required<NodeOptions>}
|
|
3635
|
+
*/
|
|
3636
|
+
options;
|
|
3637
|
+
/**
|
|
3638
|
+
* The REST for the node.
|
|
3639
|
+
* @type {RestStructure}
|
|
3640
|
+
*/
|
|
3641
|
+
rest;
|
|
3642
|
+
/**
|
|
3643
|
+
* The manager for the node.
|
|
3644
|
+
* @type {NodeManagerStructure}
|
|
3645
|
+
*/
|
|
3646
|
+
nodeManager;
|
|
3647
|
+
/**
|
|
3648
|
+
* The lyrics manager for the node.
|
|
3649
|
+
* @type {LyricsManager}
|
|
3650
|
+
*/
|
|
3651
|
+
lyricsManager;
|
|
3652
|
+
/**
|
|
3653
|
+
* The delay between reconnect attempts.
|
|
3654
|
+
* @type {number}
|
|
3655
|
+
*/
|
|
3656
|
+
retryDelay;
|
|
3657
|
+
/**
|
|
3658
|
+
* The amount of reconnect attempts left.
|
|
3659
|
+
* @type {number}
|
|
3660
|
+
*/
|
|
3661
|
+
retryAmount;
|
|
3662
|
+
/**
|
|
3663
|
+
* The WebSocket for the node.
|
|
3664
|
+
* @type {WebSocket | null}
|
|
3665
|
+
*/
|
|
3666
|
+
ws = null;
|
|
3667
|
+
/**
|
|
3668
|
+
* The state of the node.
|
|
3669
|
+
* @type {State}
|
|
3670
|
+
*/
|
|
3671
|
+
state = 7 /* Idle */;
|
|
3672
|
+
/**
|
|
3673
|
+
* The session id of the node.
|
|
3674
|
+
*/
|
|
3675
|
+
sessionId = null;
|
|
3676
|
+
/**
|
|
3677
|
+
* The interval for the reconnect.
|
|
3678
|
+
* @type {NodeJS.Timeout | null}
|
|
3679
|
+
*/
|
|
3680
|
+
reconnectTimeout = null;
|
|
3681
|
+
/**
|
|
3682
|
+
* The public stats of the node.
|
|
3683
|
+
* @type {Stats | null}
|
|
3684
|
+
*/
|
|
3685
|
+
stats = null;
|
|
3686
|
+
/**
|
|
3687
|
+
* The public info of the node.
|
|
3688
|
+
* @type {NodeInfo | null}
|
|
3689
|
+
*/
|
|
3690
|
+
info = null;
|
|
3691
|
+
/**
|
|
3692
|
+
* The session of the node.
|
|
3693
|
+
* @type {NullableLavalinkSession}
|
|
3694
|
+
*/
|
|
3695
|
+
session = {
|
|
3696
|
+
timeout: null,
|
|
3697
|
+
resuming: false
|
|
3698
|
+
};
|
|
3699
|
+
/**
|
|
3700
|
+
*
|
|
3701
|
+
* Create a new Lavalink node.
|
|
3702
|
+
* @param {NodeManagerStructure} nodeManager The manager for the node.
|
|
3703
|
+
* @param {NodeOptions} options The options for the node.
|
|
3704
|
+
* @example
|
|
3705
|
+
* ```ts
|
|
3706
|
+
* const node = new Node(nodeManager, {
|
|
3707
|
+
* host: "localhost",
|
|
3708
|
+
* port: 2333,
|
|
3709
|
+
* password: "youshallnotpass",
|
|
3710
|
+
* id: "node1",
|
|
3711
|
+
* secure: false,
|
|
3712
|
+
* retryAmount: 5,
|
|
3713
|
+
* retryDelay: 20000,
|
|
3714
|
+
* restTimeout: 10000,
|
|
3715
|
+
* sessionId: null,
|
|
3716
|
+
* });
|
|
3717
|
+
*
|
|
3718
|
+
* node.connect();
|
|
3719
|
+
* console.log(node.id); // node1
|
|
3720
|
+
* console.log(node.address); // ws://localhost:2333/v4/websocket
|
|
3721
|
+
* console.log(node.penalties); // the penalties of the node
|
|
3722
|
+
* console.log(node.state); // the state of the node
|
|
3723
|
+
* ```
|
|
3724
|
+
*/
|
|
3725
|
+
constructor(nodeManager, options) {
|
|
3726
|
+
this.options = {
|
|
3727
|
+
...options,
|
|
3728
|
+
sessionId: options.sessionId ?? "",
|
|
3729
|
+
id: options.id ?? `${options.host}:${options.port}`,
|
|
3730
|
+
restTimeout: options.restTimeout ?? 1e4,
|
|
3731
|
+
secure: options.secure ?? false,
|
|
3732
|
+
retryAmount: options.retryAmount ?? 5,
|
|
3733
|
+
retryDelay: options.retryDelay ?? 2e4
|
|
3734
|
+
};
|
|
3735
|
+
this.retryAmount = this.options.retryAmount;
|
|
3736
|
+
this.retryDelay = this.options.retryDelay;
|
|
3737
|
+
this.nodeManager = nodeManager;
|
|
3738
|
+
if (this.options.secure && this.options.port !== 443) this.options.port = 443;
|
|
3739
|
+
this.rest = Structures.Rest(this);
|
|
3740
|
+
this.lyricsManager = Structures.LyricsManager(this);
|
|
3741
|
+
}
|
|
3742
|
+
/**
|
|
3743
|
+
* The decode methods for the node.
|
|
3744
|
+
* @type {DecodeMethods}
|
|
3745
|
+
* @readonly
|
|
3746
|
+
*/
|
|
3747
|
+
decode = {
|
|
3748
|
+
single: async (track, requester) => {
|
|
3749
|
+
const raw = await this.rest.request({
|
|
3750
|
+
endpoint: "/decodetrack",
|
|
3751
|
+
params: { encodedTrack: track }
|
|
3752
|
+
});
|
|
3753
|
+
return new Track(raw, requester);
|
|
3754
|
+
},
|
|
3755
|
+
multiple: async (tracks, requester) => {
|
|
3756
|
+
const raw = await this.rest.request({
|
|
3757
|
+
endpoint: "/decodetracks",
|
|
3758
|
+
method: "POST" /* Post */,
|
|
3759
|
+
body: JSON.stringify(tracks)
|
|
3760
|
+
}) ?? [];
|
|
3761
|
+
return raw.map((track) => new Track(track, requester));
|
|
3762
|
+
}
|
|
3763
|
+
};
|
|
3764
|
+
/**
|
|
3765
|
+
* The id of the node.
|
|
3766
|
+
* @type {string}
|
|
3767
|
+
* @readonly
|
|
3768
|
+
* @example
|
|
3769
|
+
* ```ts
|
|
3770
|
+
* const node = manager.nodeManager.get("node1");
|
|
3771
|
+
* if (node) {
|
|
3772
|
+
* console.log(node.id); // node1
|
|
3773
|
+
* }
|
|
3774
|
+
* ```
|
|
3775
|
+
*/
|
|
3776
|
+
get id() {
|
|
3777
|
+
return this.options.id;
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* The socket address to connect the node.
|
|
3781
|
+
* @type {string}
|
|
3782
|
+
* @readonly
|
|
3783
|
+
* @example
|
|
3784
|
+
* ```ts
|
|
3785
|
+
* const node = manager.nodeManager.get("node1");
|
|
3786
|
+
* if (node) {
|
|
3787
|
+
* console.log(node.id); // node1
|
|
3788
|
+
* console.log(node.address); // ws://localhost:2333/v4/websocket
|
|
3789
|
+
* }
|
|
3790
|
+
* }
|
|
3791
|
+
*/
|
|
3792
|
+
get address() {
|
|
3793
|
+
return `${this.options.secure ? "wss" : "ws"}://${this.options.host}:${this.options.port}/${this.rest.version}/websocket`;
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* The penalties of the node.
|
|
3797
|
+
* @type {number}
|
|
3798
|
+
* @readonly
|
|
3799
|
+
* @example
|
|
3800
|
+
* ```ts
|
|
3801
|
+
* const node = manager.nodeManager.get("node1");
|
|
3802
|
+
* if (node) {
|
|
3803
|
+
* console.log(node.id); // node1
|
|
3804
|
+
* console.log(node.address); // ws://localhost:2333/v4/websocket
|
|
3805
|
+
* console.log(node.penalties); // the penalties of the node
|
|
3806
|
+
* }
|
|
3807
|
+
* ```
|
|
3808
|
+
*/
|
|
3809
|
+
get penalties() {
|
|
3810
|
+
if (!this.stats) return 0;
|
|
3811
|
+
const { players, cpu, frameStats } = this.stats;
|
|
3812
|
+
const cpuPenalty = Math.round(1.05 ** (100 * cpu.systemLoad) * 10 - 10);
|
|
3813
|
+
const framePenalty = frameStats ? frameStats.deficit + frameStats.nulled * 2 : 0;
|
|
3814
|
+
return players + cpuPenalty + framePenalty;
|
|
3815
|
+
}
|
|
3816
|
+
/**
|
|
3817
|
+
*
|
|
3818
|
+
* Search for a query.
|
|
3819
|
+
* @param {SearchQuery} search The query to search for.
|
|
3820
|
+
* @returns {Promise<LavalinkSearchResponse | null>}
|
|
3821
|
+
* @example
|
|
3822
|
+
* ```ts
|
|
3823
|
+
* const node = manager.nodeManager.get("node1");
|
|
3824
|
+
* if (node) {
|
|
3825
|
+
* const search = await node.search({
|
|
3826
|
+
* query: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
3827
|
+
* engine: SearchEngines.Youtube,
|
|
3828
|
+
* });
|
|
3829
|
+
*
|
|
3830
|
+
* console.log(search); // the search result
|
|
3831
|
+
* }
|
|
3832
|
+
* ```
|
|
3833
|
+
*/
|
|
3834
|
+
search(search) {
|
|
3835
|
+
search.engine ??= this.nodeManager.manager.options.defaultSearchEngine;
|
|
3836
|
+
const identifier = validateQuery(search);
|
|
3837
|
+
return this.rest.request({
|
|
3838
|
+
endpoint: "/loadtracks",
|
|
3839
|
+
params: {
|
|
3840
|
+
identifier,
|
|
3841
|
+
...search.params
|
|
3842
|
+
}
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Connect the node to the websocket.
|
|
3847
|
+
* @returns {void}
|
|
3848
|
+
* @throws {NodeError} If the client data is not valid
|
|
3849
|
+
* @example
|
|
3850
|
+
* ```ts
|
|
3851
|
+
* const node = manager.nodeManager.get("node1");
|
|
3852
|
+
* if (node) node.connect();
|
|
3853
|
+
* ```
|
|
3854
|
+
*/
|
|
3855
|
+
connect() {
|
|
3856
|
+
if (!this.nodeManager.manager.options.client)
|
|
3857
|
+
throw new NodeError({
|
|
3858
|
+
message: "No valid client data provided.",
|
|
3859
|
+
id: this.id
|
|
3860
|
+
});
|
|
3861
|
+
if (!this.nodeManager.manager.options.client.id)
|
|
3862
|
+
throw new NodeError({
|
|
3863
|
+
message: "No valid client id provided.",
|
|
3864
|
+
id: this.id
|
|
3865
|
+
});
|
|
3866
|
+
this.state = 1 /* Connecting */;
|
|
3867
|
+
const headers = {
|
|
3868
|
+
Authorization: this.options.password,
|
|
3869
|
+
"User-Id": this.nodeManager.manager.options.client.id,
|
|
3870
|
+
"Client-Name": this.nodeManager.manager.options.client.username,
|
|
3871
|
+
"User-Agent": this.rest.userAgent
|
|
3872
|
+
};
|
|
3873
|
+
if (this.options.sessionId) {
|
|
3874
|
+
headers["Session-Id"] = this.options.sessionId;
|
|
3875
|
+
this.sessionId = this.options.sessionId;
|
|
3876
|
+
this.nodeManager.manager.emit(
|
|
3877
|
+
"debug" /* Debug */,
|
|
3878
|
+
2 /* Node */,
|
|
3879
|
+
`[Socket] -> [${this.id}]: The session id is present. | Session: ${this.sessionId} | Resuming: ${this.session.resuming}`
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
this.ws = new import_ws.WebSocket(this.address, { headers: { ...headers } });
|
|
3883
|
+
this.ws.on("upgrade", onOpen.bind(this));
|
|
3884
|
+
this.ws.on("close", onClose.bind(this));
|
|
3885
|
+
this.ws.on("error", onError.bind(this));
|
|
3886
|
+
this.ws.on("message", onMessage.bind(this));
|
|
3887
|
+
this.nodeManager.manager.emit(
|
|
3888
|
+
"debug" /* Debug */,
|
|
3889
|
+
2 /* Node */,
|
|
3890
|
+
`[Socket] -> [${this.id}]: Connecting to ${this.address} | State: ${this.state} | Session: ${this.sessionId} | Resumed: ${this.session.resuming} | Penalties: ${this.penalties} | Reconnects: ${this.retryAmount} | Headers: ${JSON.stringify(headers)}`
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
*
|
|
3895
|
+
* Stop the track in player for the guild.
|
|
3896
|
+
* @param {string} guildId the guild id to stop the player
|
|
3897
|
+
* @returns {Promise<LavalinkPlayer | null>}
|
|
3898
|
+
* @example
|
|
3899
|
+
* ```ts
|
|
3900
|
+
* const node = manager.nodeManager.get("node1");
|
|
3901
|
+
* if (node) {
|
|
3902
|
+
* const player = await node.stopPlayer("guildId");
|
|
3903
|
+
* console.log(player); // the lavalink player
|
|
3904
|
+
* }
|
|
3905
|
+
* ```
|
|
3906
|
+
*/
|
|
3907
|
+
stopPlayer(guildId) {
|
|
3908
|
+
return this.rest.stopPlayer(guildId);
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
*
|
|
3912
|
+
* Update the player data.
|
|
3913
|
+
* @param {Partial<UpdatePlayerInfo>} data The player data to update.
|
|
3914
|
+
* @returns {Promise<LavalinkPlayer | null>}
|
|
3915
|
+
* @example
|
|
3916
|
+
* ```ts
|
|
3917
|
+
* const node = manager.nodeManager.get("node1");
|
|
3918
|
+
* if (node) {
|
|
3919
|
+
* const player = await node.updatePlayer({
|
|
3920
|
+
* guildId: "guildId",
|
|
3921
|
+
* noReplace: true,
|
|
3922
|
+
* playerOptions: {
|
|
3923
|
+
* paused: false,
|
|
3924
|
+
* track: { encoded: "encoded track" },
|
|
3925
|
+
* },
|
|
3926
|
+
* });
|
|
3927
|
+
*
|
|
3928
|
+
* console.log(player); // the lavalink player
|
|
3929
|
+
* }
|
|
3930
|
+
* ```
|
|
3931
|
+
*/
|
|
3932
|
+
updatePlayer(data) {
|
|
3933
|
+
return this.rest.updatePlayer(data);
|
|
3934
|
+
}
|
|
3935
|
+
/**
|
|
3936
|
+
* Destroy the player.
|
|
3937
|
+
* @returns {Promise<void>}
|
|
3938
|
+
* @param {string} guildId The guild id to destroy the player.
|
|
3939
|
+
* @example
|
|
3940
|
+
* ```ts
|
|
3941
|
+
* const node = manager.nodeManager.get("node1");
|
|
3942
|
+
* if (node) await node.destroyPlayer("guildId");
|
|
3943
|
+
* console.log("Player destroyed");
|
|
3944
|
+
* ```
|
|
3945
|
+
*/
|
|
3946
|
+
destroyPlayer(guildId) {
|
|
3947
|
+
return this.rest.destroyPlayer(guildId);
|
|
3948
|
+
}
|
|
3949
|
+
/**
|
|
3950
|
+
*
|
|
3951
|
+
* Disconnect the node from the websocket.
|
|
3952
|
+
* @param {NodeDisconnectInfo} [disconnect] The disconnect options for the node.
|
|
3953
|
+
* @returns {void}
|
|
3954
|
+
* @example
|
|
3955
|
+
* ```ts
|
|
3956
|
+
* const node = manager.nodeManager.get("node1");
|
|
3957
|
+
* if (node) node.disconnect();
|
|
3958
|
+
* console.log("Node disconnected");
|
|
3959
|
+
* ```
|
|
3960
|
+
*/
|
|
3961
|
+
disconnect(disconnect = {}) {
|
|
3962
|
+
if (this.state !== 2 /* Connected */) return;
|
|
3963
|
+
this.ws?.close(disconnect.code, disconnect.reason);
|
|
3964
|
+
this.ws?.removeAllListeners();
|
|
3965
|
+
this.ws = null;
|
|
3966
|
+
this.state = 3 /* Disconnected */;
|
|
3967
|
+
this.retryAmount = this.options.retryAmount;
|
|
3968
|
+
if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
|
|
3969
|
+
this.nodeManager.manager.emit("nodeDisconnect" /* NodeDisconnect */, this);
|
|
3970
|
+
}
|
|
3971
|
+
/**
|
|
3972
|
+
*
|
|
3973
|
+
* Destroy the node.
|
|
3974
|
+
* @param {NodeDestroyInfo} [destroy] The destroy options for the node.
|
|
3975
|
+
* @returns {void}
|
|
3976
|
+
* @example
|
|
3977
|
+
* ```ts
|
|
3978
|
+
* const node = manager.nodeManager.get("node1");
|
|
3979
|
+
* if (node) node.destroy();
|
|
3980
|
+
* console.log("Node destroyed");
|
|
3981
|
+
* ```
|
|
3982
|
+
*/
|
|
3983
|
+
destroy(destroy = {}) {
|
|
3984
|
+
if (this.state !== 2 /* Connected */) return;
|
|
3985
|
+
this.ws?.close(destroy.code, destroy.reason);
|
|
3986
|
+
this.ws?.removeAllListeners();
|
|
3987
|
+
this.ws = null;
|
|
3988
|
+
this.state = 6 /* Destroyed */;
|
|
3989
|
+
this.retryAmount = 0;
|
|
3990
|
+
if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
|
|
3991
|
+
this.nodeManager.manager.emit("nodeDestroy" /* NodeDestroy */, this, destroy);
|
|
3992
|
+
this.nodeManager.delete(this.id);
|
|
3993
|
+
}
|
|
3994
|
+
/**
|
|
3995
|
+
*
|
|
3996
|
+
* Update the session for the node
|
|
3997
|
+
* @param {boolean} resuming Enable resuming for the session.
|
|
3998
|
+
* @param {number | null} timeout The timeout for the session.
|
|
3999
|
+
* @returns {Promise<LavalinkSession | null>}
|
|
4000
|
+
* @example
|
|
4001
|
+
* ```ts
|
|
4002
|
+
* const node = manager.nodeManager.get("node1");
|
|
4003
|
+
* if (node) {
|
|
4004
|
+
* const session = await node.updateSession(true, 60);
|
|
4005
|
+
* console.log(session); // the lavalink session
|
|
4006
|
+
* }
|
|
4007
|
+
* ```
|
|
4008
|
+
*/
|
|
4009
|
+
async updateSession(resuming, timeout = null) {
|
|
4010
|
+
if (!this.sessionId) return null;
|
|
4011
|
+
const session = await this.rest.updateSession(resuming, timeout);
|
|
4012
|
+
if (session) this.session = session;
|
|
4013
|
+
return session;
|
|
4014
|
+
}
|
|
4015
|
+
/**
|
|
4016
|
+
* Reconnect the node.
|
|
4017
|
+
* @returns {void}
|
|
4018
|
+
* @throws {NodeError} If the node is not connected
|
|
4019
|
+
* @example
|
|
4020
|
+
* ```ts
|
|
4021
|
+
* const node = manager.nodeManager.get("node1");
|
|
4022
|
+
* if (node) node.reconnect();
|
|
4023
|
+
* console.log("Node reconnected");
|
|
4024
|
+
* ```
|
|
4025
|
+
*/
|
|
4026
|
+
reconnect() {
|
|
4027
|
+
this.state = 7 /* Idle */;
|
|
4028
|
+
this.nodeManager.manager.emit("nodeReconnecting" /* NodeReconnecting */, this, this.retryAmount, this.retryDelay);
|
|
4029
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
4030
|
+
this.reconnectTimeout = null;
|
|
4031
|
+
if (this.retryAmount === 0) {
|
|
4032
|
+
this.destroy({
|
|
4033
|
+
code: 1e3 /* NormalClosure */,
|
|
4034
|
+
reason: "Node-Destroy" /* Destroy */
|
|
4035
|
+
});
|
|
4036
|
+
this.nodeManager.manager.emit(
|
|
4037
|
+
"nodeError" /* NodeError */,
|
|
4038
|
+
this,
|
|
4039
|
+
new NodeError({
|
|
4040
|
+
message: `Failed to reconnect after ${this.options.retryAmount} retries.`,
|
|
4041
|
+
id: this.id
|
|
4042
|
+
})
|
|
4043
|
+
);
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
this.ws?.removeAllListeners();
|
|
4047
|
+
this.ws = null;
|
|
4048
|
+
this.retryAmount--;
|
|
4049
|
+
this.connect();
|
|
4050
|
+
}, this.options.retryDelay);
|
|
4051
|
+
}
|
|
4052
|
+
/**
|
|
4053
|
+
*
|
|
4054
|
+
* Convert the node to JSON.
|
|
4055
|
+
* @returns {NodeJson} The JSON representation of the node.
|
|
4056
|
+
* @example
|
|
4057
|
+
* ```ts
|
|
4058
|
+
* const node = manager.nodeManager.get("node1");
|
|
4059
|
+
* if (node) {
|
|
4060
|
+
* const json = node.toJSON();
|
|
4061
|
+
* console.log(json);
|
|
4062
|
+
* }
|
|
4063
|
+
* ```
|
|
4064
|
+
*/
|
|
4065
|
+
toJSON() {
|
|
4066
|
+
return {
|
|
4067
|
+
id: this.id,
|
|
4068
|
+
sessionId: this.sessionId
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
|
|
4073
|
+
// src/classes/queue/adapters/memory.ts
|
|
4074
|
+
var MemoryAdapter = class extends StorageAdapter {
|
|
4075
|
+
/**
|
|
4076
|
+
* Memory storage.
|
|
4077
|
+
* @type {Map<string, QueueJson>}
|
|
4078
|
+
* @private
|
|
4079
|
+
* @readonly
|
|
4080
|
+
* @internal
|
|
4081
|
+
*/
|
|
4082
|
+
storage = /* @__PURE__ */ new Map();
|
|
4083
|
+
get(key) {
|
|
4084
|
+
return this.parse(this.storage.get(key));
|
|
4085
|
+
}
|
|
4086
|
+
set(key, value) {
|
|
4087
|
+
this.storage.set(key, this.stringify(value));
|
|
4088
|
+
}
|
|
4089
|
+
delete(key) {
|
|
4090
|
+
return this.storage.delete(key);
|
|
4091
|
+
}
|
|
4092
|
+
clear() {
|
|
4093
|
+
this.storage.clear();
|
|
4094
|
+
}
|
|
4095
|
+
has(key) {
|
|
4096
|
+
return this.storage.has(key);
|
|
4097
|
+
}
|
|
4098
|
+
parse(value) {
|
|
4099
|
+
return value;
|
|
4100
|
+
}
|
|
4101
|
+
stringify(value) {
|
|
4102
|
+
return value;
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
|
|
4106
|
+
// src/classes/Hoshimi.ts
|
|
4107
|
+
var import_node_events = require("events");
|
|
4108
|
+
|
|
4109
|
+
// src/util/functions/autoplay.ts
|
|
4110
|
+
var maxTracks = 10;
|
|
4111
|
+
async function autoplayFn(player, lastTrack) {
|
|
4112
|
+
if (!lastTrack) return;
|
|
4113
|
+
const isEnabled = !!player.data.get("enabledAutoplay") || player.manager.options.queueOptions.autoPlay;
|
|
4114
|
+
if (!isEnabled) return;
|
|
4115
|
+
const filter = (tracks) => tracks.filter(
|
|
4116
|
+
(track) => !(player.queue.history.some((t) => t.info.identifier === track.info.identifier) || lastTrack.info.identifier === track.info.identifier)
|
|
4117
|
+
);
|
|
4118
|
+
switch (lastTrack.info.sourceName) {
|
|
4119
|
+
case "spotify" /* Spotify */: {
|
|
4120
|
+
const filtered = player.queue.history.filter(({ info }) => info.sourceName === "spotify" /* Spotify */).slice(0, 1);
|
|
4121
|
+
if (!filtered.length) filtered.push(lastTrack);
|
|
4122
|
+
const ids = filtered.map(
|
|
4123
|
+
({ info }) => info.identifier ?? info.uri?.split("/").reverse()?.[0] ?? info.uri?.split("/").reverse()?.[1]
|
|
4124
|
+
);
|
|
4125
|
+
const res = await player.search({
|
|
4126
|
+
query: `seed_tracks=${ids.join(",")}`,
|
|
4127
|
+
engine: "sprec" /* SpotifyRecommendations */,
|
|
4128
|
+
requester: lastTrack.requester
|
|
4129
|
+
});
|
|
4130
|
+
if (res.tracks.length) {
|
|
4131
|
+
const index = Math.floor(Math.random() * res.tracks.length);
|
|
4132
|
+
const track = filter(res.tracks)[index];
|
|
4133
|
+
if (!track) return;
|
|
4134
|
+
player.queue.add(track);
|
|
4135
|
+
}
|
|
4136
|
+
break;
|
|
4137
|
+
}
|
|
4138
|
+
case "youtube" /* Youtube */:
|
|
4139
|
+
case "youtubemusic" /* YoutubeMusic */: {
|
|
4140
|
+
const search = `https://www.youtube.com/watch?v=${lastTrack.info.identifier}&list=RD${lastTrack.info.identifier}`;
|
|
4141
|
+
const res = await player.search({
|
|
4142
|
+
query: search,
|
|
4143
|
+
requester: lastTrack.requester
|
|
4144
|
+
});
|
|
4145
|
+
if (res.tracks.length) {
|
|
4146
|
+
const random = Math.floor(Math.random() * res.tracks.length);
|
|
4147
|
+
const tracks = filter(res.tracks).slice(random, random + maxTracks);
|
|
4148
|
+
player.queue.add(tracks);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
// src/classes/Hoshimi.ts
|
|
4155
|
+
var Hoshimi = class extends import_node_events.EventEmitter {
|
|
4156
|
+
/**
|
|
4157
|
+
* The options for the manager.
|
|
4158
|
+
* @type {HoshimiOptions}
|
|
4159
|
+
*/
|
|
4160
|
+
options;
|
|
4161
|
+
/**
|
|
4162
|
+
* The players for the manager.
|
|
4163
|
+
* @type {Collection<string, PlayerStructure>}
|
|
4164
|
+
* @readonly
|
|
4165
|
+
*/
|
|
4166
|
+
players = new Collection();
|
|
4167
|
+
/**
|
|
4168
|
+
* THe node manager for the manager.
|
|
4169
|
+
* @type {NodeManager}
|
|
4170
|
+
* @readonly
|
|
4171
|
+
*/
|
|
4172
|
+
nodeManager;
|
|
4173
|
+
/**
|
|
4174
|
+
* If the manager is ready.
|
|
4175
|
+
* @type {boolean}
|
|
4176
|
+
*/
|
|
4177
|
+
ready = false;
|
|
4178
|
+
/**
|
|
4179
|
+
* The constructor for the manager.
|
|
4180
|
+
* @param {HoshimiOptions} options The options for the manager.
|
|
4181
|
+
* @throws {ManagerError} If the options are not provided.
|
|
4182
|
+
* @example
|
|
4183
|
+
* ```ts
|
|
4184
|
+
* const manager = new Hoshimi({
|
|
4185
|
+
* nodes: [
|
|
4186
|
+
* {
|
|
4187
|
+
* host: "localhost",
|
|
4188
|
+
* port: 2333,
|
|
4189
|
+
* password: "youshallnotpass",
|
|
4190
|
+
* secure: false,
|
|
4191
|
+
* },
|
|
4192
|
+
* ],
|
|
4193
|
+
* client: {
|
|
4194
|
+
* id: "clientId",
|
|
4195
|
+
* username: "clientUsername",
|
|
4196
|
+
* },
|
|
4197
|
+
* defaultSearchEngine: SearchEngines.Youtube,
|
|
4198
|
+
* restOptions: {
|
|
4199
|
+
* resumeTimeout: 10000,
|
|
4200
|
+
* },
|
|
4201
|
+
* nodeOptions: {
|
|
4202
|
+
* userAgent: HoshimiAgent,
|
|
4203
|
+
* resumable: false,
|
|
4204
|
+
* resumeByLibrary: false,
|
|
4205
|
+
* },
|
|
4206
|
+
* queueOptions: {
|
|
4207
|
+
* maxPreviousTracks: 25,
|
|
4208
|
+
* autoplayFn: autoplayFn,
|
|
4209
|
+
* autoPlay: false,
|
|
4210
|
+
* },
|
|
4211
|
+
* });
|
|
4212
|
+
*
|
|
4213
|
+
* console.log(manager); // The manager instance
|
|
4214
|
+
* ```
|
|
4215
|
+
*/
|
|
4216
|
+
constructor(options) {
|
|
4217
|
+
super();
|
|
4218
|
+
if (!options) throw new ManagerError("You must provide the options for the manager.");
|
|
4219
|
+
this.options = {
|
|
4220
|
+
...options,
|
|
4221
|
+
defaultSearchEngine: options.defaultSearchEngine ?? "ytsearch" /* Youtube */,
|
|
4222
|
+
restOptions: {
|
|
4223
|
+
resumeTimeout: options.restOptions?.resumeTimeout ?? 1e4
|
|
4224
|
+
},
|
|
4225
|
+
nodeOptions: {
|
|
4226
|
+
userAgent: options.nodeOptions?.userAgent ?? HoshimiAgent,
|
|
4227
|
+
resumable: options.nodeOptions?.resumable ?? false,
|
|
4228
|
+
resumeByLibrary: options.nodeOptions?.resumeByLibrary ?? false,
|
|
4229
|
+
resumeTimeout: options.nodeOptions?.resumeTimeout ?? 1e4
|
|
4230
|
+
},
|
|
4231
|
+
queueOptions: {
|
|
4232
|
+
maxPreviousTracks: options.queueOptions?.maxPreviousTracks ?? 25,
|
|
4233
|
+
autoplayFn: options.queueOptions?.autoplayFn ?? autoplayFn,
|
|
4234
|
+
autoPlay: options.queueOptions?.autoPlay ?? false,
|
|
4235
|
+
storage: options.queueOptions?.storage ?? new MemoryAdapter()
|
|
4236
|
+
},
|
|
4237
|
+
client: {
|
|
4238
|
+
id: options.client?.id ?? "",
|
|
4239
|
+
username: options.client?.username ?? "hoshimi-client"
|
|
4240
|
+
}
|
|
4241
|
+
};
|
|
4242
|
+
validateManagerOptions(this.options);
|
|
4243
|
+
this.nodeManager = Structures.NodeManager(this);
|
|
4244
|
+
}
|
|
4245
|
+
/**
|
|
4246
|
+
* Check if the manager is useable.
|
|
4247
|
+
* @returns {boolean} If the manager is useable.
|
|
4248
|
+
* @example
|
|
4249
|
+
* ```ts
|
|
4250
|
+
* if (manager.isUseable()) {
|
|
4251
|
+
* console.log("The manager is useable.");
|
|
4252
|
+
* } else {
|
|
4253
|
+
* console.log("The manager is not useable.");
|
|
4254
|
+
* }
|
|
4255
|
+
* ```
|
|
4256
|
+
*/
|
|
4257
|
+
isUseable() {
|
|
4258
|
+
const nodes = this.nodeManager.nodes.filter((node) => node.state === 2 /* Connected */);
|
|
4259
|
+
return this.ready && nodes.length > 0;
|
|
4260
|
+
}
|
|
4261
|
+
/**
|
|
4262
|
+
*
|
|
4263
|
+
* Get the player for the guild.
|
|
4264
|
+
* @param {string} guildId The guild id to get the player.
|
|
4265
|
+
* @returns {PlayerStructure | undefined} The player for the guild.
|
|
4266
|
+
* @example
|
|
4267
|
+
* ```ts
|
|
4268
|
+
* const player = manager.getPlayer(guildId);
|
|
4269
|
+
* if (player) {
|
|
4270
|
+
* console.log(`The player for ${guildId} is ${player}`);
|
|
4271
|
+
* } else {
|
|
4272
|
+
* console.log(`The player for ${guildId} is not found.`);
|
|
4273
|
+
* }
|
|
4274
|
+
* ```
|
|
4275
|
+
*/
|
|
4276
|
+
getPlayer(guildId) {
|
|
4277
|
+
return this.players.get(guildId);
|
|
4278
|
+
}
|
|
4279
|
+
/**
|
|
4280
|
+
* Delete the player for the guild.
|
|
4281
|
+
* @param {string} guildId The guild id to delete the player.
|
|
4282
|
+
* @returns {boolean} If the player was deleted.
|
|
4283
|
+
* @example
|
|
4284
|
+
* ```ts
|
|
4285
|
+
* const player = manager.deletePlayer(guildId);
|
|
4286
|
+
* if (player) {
|
|
4287
|
+
* console.log(`The player for ${guildId} was deleted.`);
|
|
4288
|
+
* } else {
|
|
4289
|
+
* console.log(`The player for ${guildId} was not found.`);
|
|
4290
|
+
* }
|
|
4291
|
+
* ```
|
|
4292
|
+
*/
|
|
4293
|
+
deletePlayer(guildId) {
|
|
4294
|
+
return this.players.delete(guildId);
|
|
4295
|
+
}
|
|
4296
|
+
/**
|
|
4297
|
+
*
|
|
4298
|
+
* Handle the raw packet for voice state and voice server updates.
|
|
4299
|
+
* @param {GatewayPackets} packet The packet to handle
|
|
4300
|
+
* @returns {Promise<void>}
|
|
4301
|
+
* @example
|
|
4302
|
+
* ```ts
|
|
4303
|
+
* client.on("raw", (packet) => manager.updateVoiceState(packet));
|
|
4304
|
+
* ```
|
|
4305
|
+
*/
|
|
4306
|
+
async updateVoiceState(packet) {
|
|
4307
|
+
if (!this.ready) {
|
|
4308
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Manager] The manager is not ready.");
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
if (!("t" in packet)) {
|
|
4312
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Voice] The packet does not have a type.");
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
switch (packet.t) {
|
|
4316
|
+
case "CHANNEL_DELETE": {
|
|
4317
|
+
const data = packet.d;
|
|
4318
|
+
const player = this.getPlayer(data.guild_id);
|
|
4319
|
+
if (!player) {
|
|
4320
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Voice] The player is not found.");
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
if (data.id === player.voiceId) {
|
|
4324
|
+
this.emit(
|
|
4325
|
+
"debug" /* Debug */,
|
|
4326
|
+
3 /* Player */,
|
|
4327
|
+
`[Player] -> [Voice] The channel ${data.id} was deleted, disconnecting the player.`
|
|
4328
|
+
);
|
|
4329
|
+
await player.destroy("Player-VoiceChannelDeleted" /* VoiceChannelDeleted */);
|
|
4330
|
+
} else {
|
|
4331
|
+
this.emit(
|
|
4332
|
+
"debug" /* Debug */,
|
|
4333
|
+
3 /* Player */,
|
|
4334
|
+
`[Player] -> [Voice] The channel ${data.id} was deleted, but it is not the player's channel.`
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
break;
|
|
4338
|
+
}
|
|
4339
|
+
case "VOICE_SERVER_UPDATE":
|
|
4340
|
+
case "VOICE_STATE_UPDATE":
|
|
4341
|
+
{
|
|
4342
|
+
const data = packet.d;
|
|
4343
|
+
if (!("guild_id" in data)) {
|
|
4344
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Voice] The guild id is missing.");
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
if ("user_id" in data && data.user_id !== this.options.client?.id) {
|
|
4348
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Voice] The user id does not match the client id.");
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
const player = this.getPlayer(data.guild_id);
|
|
4352
|
+
if (!player) {
|
|
4353
|
+
this.emit("debug" /* Debug */, 3 /* Player */, "[Player] -> [Voice] The player is not found.");
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
4356
|
+
if ("session_id" in data) player.voice.sessionId = data.session_id;
|
|
4357
|
+
if ("token" in data) player.voice.token = data.token;
|
|
4358
|
+
if ("endpoint" in data) player.voice.endpoint = data.endpoint;
|
|
4359
|
+
if (player.voice.sessionId && player.voice.token && player.voice.endpoint) {
|
|
4360
|
+
await player.node.updatePlayer({
|
|
4361
|
+
guildId: data.guild_id,
|
|
4362
|
+
playerOptions: {
|
|
4363
|
+
voice: player.voice
|
|
4364
|
+
}
|
|
4365
|
+
});
|
|
4366
|
+
this.emit(
|
|
4367
|
+
"debug" /* Debug */,
|
|
4368
|
+
3 /* Player */,
|
|
4369
|
+
`[Player] -> [Voice] Updated the player voice for: ${data.guild_id} | Session: ${player.voice.sessionId} | Token: ${player.voice.token} | Endpoint: ${player.voice.endpoint}`
|
|
4370
|
+
);
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
this.emit("debug" /* Debug */, 3 /* Player */, `[Player] -> [Voice] The player voice is missing for: ${data.guild_id}`);
|
|
4374
|
+
}
|
|
4375
|
+
break;
|
|
4376
|
+
default:
|
|
4377
|
+
break;
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
/**
|
|
4381
|
+
*
|
|
4382
|
+
* Initialize the manager.
|
|
4383
|
+
* @param {ClientData} info The client data to use.
|
|
4384
|
+
* @returns {void}
|
|
4385
|
+
* @example
|
|
4386
|
+
* ```ts
|
|
4387
|
+
* manager.init({
|
|
4388
|
+
* id: "clientId",
|
|
4389
|
+
* username: "clientUsername",
|
|
4390
|
+
* });
|
|
4391
|
+
* ```
|
|
4392
|
+
*/
|
|
4393
|
+
init(info) {
|
|
4394
|
+
if (this.ready) return;
|
|
4395
|
+
this.options.client = {
|
|
4396
|
+
...this.options.client,
|
|
4397
|
+
...info
|
|
4398
|
+
};
|
|
4399
|
+
if (!this.options.client.id) throw new ManagerError("You must provide the client id.");
|
|
4400
|
+
if (typeof this.options.client.id !== "string") throw new OptionError("The client info 'info.client.id': must be a string.");
|
|
4401
|
+
let amount = 0;
|
|
4402
|
+
for (const options of this.options.nodes) {
|
|
4403
|
+
const node = this.nodeManager.create(options);
|
|
4404
|
+
try {
|
|
4405
|
+
node.connect();
|
|
4406
|
+
amount++;
|
|
4407
|
+
} catch (error) {
|
|
4408
|
+
this.emit("nodeError" /* NodeError */, node, error);
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
this.ready = amount > 0;
|
|
4412
|
+
this.emit(
|
|
4413
|
+
"debug" /* Debug */,
|
|
4414
|
+
3 /* Player */,
|
|
4415
|
+
`[Manager] -> [Init] The manager is ready: ${this.ready} | Nodes: ${amount} of ${this.nodeManager.nodes.size}`
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
4418
|
+
/**
|
|
4419
|
+
*
|
|
4420
|
+
* Create a new player.
|
|
4421
|
+
* @param {PlayerOptions} options The options for the player.
|
|
4422
|
+
* @returns {Player} The created player.
|
|
4423
|
+
* @example
|
|
4424
|
+
* ```ts
|
|
4425
|
+
* const player = manager.createPlayer({
|
|
4426
|
+
* guildId: "guildId",
|
|
4427
|
+
* voiceId: "voiceId",
|
|
4428
|
+
* });
|
|
4429
|
+
*
|
|
4430
|
+
* console.log(player); // The created player
|
|
4431
|
+
*
|
|
4432
|
+
* player.connect();
|
|
4433
|
+
* player.play(track);
|
|
4434
|
+
* ```
|
|
4435
|
+
*/
|
|
4436
|
+
createPlayer(options) {
|
|
4437
|
+
const oldPlayer = this.getPlayer(options.guildId);
|
|
4438
|
+
if (oldPlayer) return oldPlayer;
|
|
4439
|
+
const player = Structures.Player(this, options);
|
|
4440
|
+
this.players.set(options.guildId, player);
|
|
4441
|
+
this.emit("playerCreate" /* PlayerCreate */, player);
|
|
4442
|
+
return player;
|
|
4443
|
+
}
|
|
4444
|
+
/**
|
|
4445
|
+
*
|
|
4446
|
+
* Search for a track or playlist.
|
|
4447
|
+
* @param {SearchOptions} options The options for the search.
|
|
4448
|
+
* @returns {Promise<QueryResult>} The search result.
|
|
4449
|
+
* @example
|
|
4450
|
+
* ```ts
|
|
4451
|
+
* const result = await manager.search({
|
|
4452
|
+
* query: "track name",
|
|
4453
|
+
* engine: SearchEngines.Youtube,
|
|
4454
|
+
* });
|
|
4455
|
+
*
|
|
4456
|
+
* console.log(result); // The search result
|
|
4457
|
+
* ```
|
|
4458
|
+
*/
|
|
4459
|
+
async search(options) {
|
|
4460
|
+
let node = null;
|
|
4461
|
+
if (options.node) {
|
|
4462
|
+
const nodeId = typeof options.node === "string" ? options.node : options.node.id;
|
|
4463
|
+
node = this.nodeManager.get(nodeId) ?? null;
|
|
4464
|
+
} else {
|
|
4465
|
+
node = this.nodeManager.getLeastUsed();
|
|
4466
|
+
}
|
|
4467
|
+
if (!node) throw new ManagerError("No nodes are available.");
|
|
4468
|
+
const res = await node.search(options);
|
|
4469
|
+
if (!res)
|
|
4470
|
+
return {
|
|
4471
|
+
loadType: "empty" /* Empty */,
|
|
4472
|
+
exception: null,
|
|
4473
|
+
playlist: null,
|
|
4474
|
+
pluginInfo: null,
|
|
4475
|
+
tracks: []
|
|
4476
|
+
};
|
|
4477
|
+
this.emit(
|
|
4478
|
+
"debug" /* Debug */,
|
|
4479
|
+
1 /* Manager */,
|
|
4480
|
+
`[Manager] -> [Search] Searching for: ${options.query} (${options.engine ?? "unknown"}) | Result: ${JSON.stringify(res)}`
|
|
4481
|
+
);
|
|
4482
|
+
switch (res.loadType) {
|
|
4483
|
+
case "empty" /* Empty */: {
|
|
4484
|
+
return {
|
|
4485
|
+
loadType: res.loadType,
|
|
4486
|
+
exception: null,
|
|
4487
|
+
playlist: null,
|
|
4488
|
+
pluginInfo: null,
|
|
4489
|
+
tracks: []
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4492
|
+
case "error" /* Error */: {
|
|
4493
|
+
return {
|
|
4494
|
+
loadType: res.loadType,
|
|
4495
|
+
exception: res.data,
|
|
4496
|
+
playlist: null,
|
|
4497
|
+
pluginInfo: null,
|
|
4498
|
+
tracks: []
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
case "playlist" /* Playlist */: {
|
|
4502
|
+
return {
|
|
4503
|
+
loadType: res.loadType,
|
|
4504
|
+
exception: null,
|
|
4505
|
+
playlist: res.data,
|
|
4506
|
+
pluginInfo: res.data.pluginInfo,
|
|
4507
|
+
tracks: res.data.tracks.map((t) => new Track(t, options.requester))
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
case "search" /* Search */: {
|
|
4511
|
+
return {
|
|
4512
|
+
loadType: res.loadType,
|
|
4513
|
+
exception: null,
|
|
4514
|
+
playlist: null,
|
|
4515
|
+
pluginInfo: null,
|
|
4516
|
+
tracks: res.data.map((t) => new Track(t, options.requester))
|
|
4517
|
+
};
|
|
4518
|
+
}
|
|
4519
|
+
case "track" /* Track */: {
|
|
4520
|
+
return {
|
|
4521
|
+
loadType: res.loadType,
|
|
4522
|
+
exception: null,
|
|
4523
|
+
playlist: null,
|
|
4524
|
+
pluginInfo: res.data.pluginInfo,
|
|
4525
|
+
tracks: [new Track(res.data, options.requester)]
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
function createHoshimi(...args) {
|
|
4532
|
+
return new Hoshimi(...args);
|
|
4533
|
+
}
|
|
4534
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4535
|
+
0 && (module.exports = {
|
|
4536
|
+
AudioOutput,
|
|
4537
|
+
DSPXPluginFilter,
|
|
4538
|
+
DebugLevels,
|
|
4539
|
+
DestroyReasons,
|
|
4540
|
+
Events,
|
|
4541
|
+
FilterManager,
|
|
4542
|
+
FilterType,
|
|
4543
|
+
Hoshimi,
|
|
4544
|
+
HttpMethods,
|
|
4545
|
+
HttpStatusCodes,
|
|
4546
|
+
LavalinkPluginFilter,
|
|
4547
|
+
LoadType,
|
|
4548
|
+
LoopMode,
|
|
4549
|
+
ManagerError,
|
|
4550
|
+
MemoryAdapter,
|
|
4551
|
+
Node,
|
|
4552
|
+
NodeDestroyReasons,
|
|
4553
|
+
NodeError,
|
|
4554
|
+
NodeSortTypes,
|
|
4555
|
+
OpCodes,
|
|
4556
|
+
OptionError,
|
|
4557
|
+
Player,
|
|
4558
|
+
PlayerError,
|
|
4559
|
+
PlayerEventType,
|
|
4560
|
+
PluginInfoType,
|
|
4561
|
+
PluginNames,
|
|
4562
|
+
Queue,
|
|
4563
|
+
ResolveError,
|
|
4564
|
+
Rest,
|
|
4565
|
+
SearchEngines,
|
|
4566
|
+
Severity,
|
|
4567
|
+
SourceNames,
|
|
4568
|
+
State,
|
|
4569
|
+
StorageAdapter,
|
|
4570
|
+
StorageError,
|
|
4571
|
+
Structures,
|
|
4572
|
+
Track,
|
|
4573
|
+
TrackEndReason,
|
|
4574
|
+
UnresolvedTrack,
|
|
4575
|
+
WebsocketCloseCodes,
|
|
4576
|
+
createHoshimi
|
|
4577
|
+
});
|