lavalink-client 2.4.7 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/cjs/structures/CustomSearches/BandCampSearch.js +3 -1
- package/dist/cjs/structures/LavalinkManager.js +4 -4
- package/dist/cjs/structures/LavalinkManagerStatics.js +21 -0
- package/dist/cjs/structures/Node.d.ts +0 -12
- package/dist/cjs/structures/Node.js +32 -48
- package/dist/cjs/structures/Player.js +39 -0
- package/dist/cjs/structures/Types/Manager.d.ts +2 -0
- package/dist/cjs/structures/Types/Player.d.ts +2 -0
- package/dist/cjs/structures/Types/Track.d.ts +1 -1
- package/dist/cjs/structures/Types/Utils.d.ts +5 -5
- package/dist/cjs/structures/Utils.d.ts +2 -2
- package/dist/cjs/structures/Utils.js +30 -6
- package/dist/esm/structures/CustomSearches/BandCampSearch.js +3 -1
- package/dist/esm/structures/LavalinkManager.js +4 -4
- package/dist/esm/structures/LavalinkManagerStatics.js +21 -0
- package/dist/esm/structures/Node.d.ts +0 -12
- package/dist/esm/structures/Node.js +32 -48
- package/dist/esm/structures/Player.js +39 -0
- package/dist/esm/structures/Types/Manager.d.ts +2 -0
- package/dist/esm/structures/Types/Player.d.ts +2 -0
- package/dist/esm/structures/Types/Track.d.ts +1 -1
- package/dist/esm/structures/Types/Utils.d.ts +5 -5
- package/dist/esm/structures/Utils.d.ts +2 -2
- package/dist/esm/structures/Utils.js +30 -6
- package/dist/types/structures/Node.d.ts +0 -12
- package/dist/types/structures/Types/Manager.d.ts +2 -0
- package/dist/types/structures/Types/Player.d.ts +2 -0
- package/dist/types/structures/Types/Track.d.ts +1 -1
- package/dist/types/structures/Types/Utils.d.ts +5 -5
- package/dist/types/structures/Utils.d.ts +2 -2
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -819,3 +819,27 @@ if(previousTrack) {
|
|
|
819
819
|
- remove structuredClone so that it works in bun more stable
|
|
820
820
|
- Player Options Validation also allows single property objects
|
|
821
821
|
- Some typos were fixed
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
## **Version 2.5.0**
|
|
826
|
+
- Deps got updated
|
|
827
|
+
- Handling of parsing url got updated to use the URL object
|
|
828
|
+
- Flowerytts requests needs to be changed now:
|
|
829
|
+
|
|
830
|
+
```js
|
|
831
|
+
const query = "Hello World How are you?";
|
|
832
|
+
const voice = "Ava";
|
|
833
|
+
const speed = "1.0";
|
|
834
|
+
|
|
835
|
+
const fttsParams = new URLSearchParams();
|
|
836
|
+
if(voice) fttsParams.append("voice", voice);
|
|
837
|
+
if(speed) fttsParams.append("speed", speed);
|
|
838
|
+
|
|
839
|
+
const response = await player.search({
|
|
840
|
+
// For flowerytts you need to send a URL
|
|
841
|
+
// and if you want to add optiojns, this is how you add the params to the query..
|
|
842
|
+
query: `${encodeURI(query)}${fttsParams.size ? `?${fttsParams.toString()}` : ""}`,
|
|
843
|
+
source: "ftts"
|
|
844
|
+
}, interaction.user);
|
|
845
|
+
```
|
|
@@ -8,7 +8,9 @@ const bandCampSearch = async (player, query, requestUser) => {
|
|
|
8
8
|
console.log(`Lavalink-Client-Debug | SEARCHING | - ${query} on lavalink-client`);
|
|
9
9
|
player.LavalinkManager.utils.validateQueryString(player.node, query);
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
11
|
+
const requestUrl = new URL("https://bandcamp.com/api/nusearch/2/autocomplete");
|
|
12
|
+
requestUrl.searchParams.append("q", query);
|
|
13
|
+
const data = await fetch(requestUrl.toString(), {
|
|
12
14
|
headers: {
|
|
13
15
|
'User-Agent': 'android-async-http/1.4.1 (http://loopj.com/android-async-http)',
|
|
14
16
|
'Cookie': '$Version=1'
|
|
@@ -495,7 +495,7 @@ class LavalinkManager extends events_1.EventEmitter {
|
|
|
495
495
|
functionLayer: "LavalinkManager > sendRawData()",
|
|
496
496
|
});
|
|
497
497
|
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
498
|
-
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function,
|
|
498
|
+
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice });
|
|
499
499
|
}
|
|
500
500
|
else {
|
|
501
501
|
await player.node.updatePlayer({
|
|
@@ -505,8 +505,8 @@ class LavalinkManager extends events_1.EventEmitter {
|
|
|
505
505
|
token: update.token,
|
|
506
506
|
endpoint: update.endpoint,
|
|
507
507
|
sessionId: sessionId2Use,
|
|
508
|
-
}
|
|
509
|
-
}
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
510
|
});
|
|
511
511
|
if (this.options?.advancedOptions?.enableDebugEvents) {
|
|
512
512
|
this.emit("debug", Constants_1.DebugEvents.NoAudioDebug, {
|
|
@@ -516,7 +516,7 @@ class LavalinkManager extends events_1.EventEmitter {
|
|
|
516
516
|
});
|
|
517
517
|
}
|
|
518
518
|
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
519
|
-
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function,
|
|
519
|
+
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
|
|
520
520
|
}
|
|
521
521
|
return;
|
|
522
522
|
}
|
|
@@ -39,11 +39,24 @@ exports.DefaultSources = {
|
|
|
39
39
|
"dz": "dzsearch",
|
|
40
40
|
"dzsearch": "dzsearch",
|
|
41
41
|
"dzisrc": "dzisrc",
|
|
42
|
+
"dzrec": "dzrec",
|
|
42
43
|
// yandexmusic
|
|
43
44
|
"yandex music": "ymsearch",
|
|
44
45
|
"yandexmusic": "ymsearch",
|
|
45
46
|
"yandex": "ymsearch",
|
|
46
47
|
"ymsearch": "ymsearch",
|
|
48
|
+
"ymrec": "ymrec",
|
|
49
|
+
// VK Music (lavasrc)
|
|
50
|
+
"vksearch": "vksearch",
|
|
51
|
+
"vkmusic": "vksearch",
|
|
52
|
+
"vk music": "vksearch",
|
|
53
|
+
"vkrec": "vkrec",
|
|
54
|
+
"vk": "vksearch",
|
|
55
|
+
// Qobuz (lavasrc)
|
|
56
|
+
"qbsearch": "qbsearch",
|
|
57
|
+
"qobuz": "qbsearch",
|
|
58
|
+
"qbisrc": "qbisrc",
|
|
59
|
+
"qbrec": "qbrec",
|
|
47
60
|
// speak PLUGIN
|
|
48
61
|
"speak": "speak",
|
|
49
62
|
"tts": "tts",
|
|
@@ -66,6 +79,12 @@ exports.DefaultSources = {
|
|
|
66
79
|
"https": "https",
|
|
67
80
|
"link": "link",
|
|
68
81
|
"uri": "uri",
|
|
82
|
+
// tidal
|
|
83
|
+
"tidal": "tdsearch",
|
|
84
|
+
"td": "tdsearch",
|
|
85
|
+
"tidal music": "tdsearch",
|
|
86
|
+
"tdsearch": "tdsearch",
|
|
87
|
+
"tdrec": "tdrec",
|
|
69
88
|
// jiosaavn
|
|
70
89
|
"jiosaavn": "jssearch",
|
|
71
90
|
"js": "jssearch",
|
|
@@ -118,6 +137,8 @@ exports.SourceLinksRegexes = {
|
|
|
118
137
|
SpotifyAlbumRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?album\/(?<identifier>[a-zA-Z0-9-_]+)/,
|
|
119
138
|
AllSpotifyRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?(?<type>track|album|playlist|artist|episode|show)\/(?<identifier>[a-zA-Z0-9-_]+)/,
|
|
120
139
|
appleMusic: /https?:\/\/?(?:www\.)?music\.apple\.com\/(\S+)/,
|
|
140
|
+
/** From tidal */
|
|
141
|
+
tidal: /https?:\/\/?(?:www\.)?(?:tidal|listen)\.tidal\.com\/(?<type>track|album|playlist|artist)\/(?<identifier>[a-zA-Z0-9-_]+)/,
|
|
121
142
|
/** From jiosaavn-plugin */
|
|
122
143
|
jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_\/,]+)/,
|
|
123
144
|
/** FROM DUNCTE BOT PLUGIN */
|
|
@@ -53,18 +53,6 @@ export declare class LavalinkNode {
|
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
55
55
|
constructor(options: LavalinkNodeOptions, manager: NodeManager);
|
|
56
|
-
/**
|
|
57
|
-
* Parse url params correctly for lavalink requests, including support for urls and uris.
|
|
58
|
-
* @param url input url object
|
|
59
|
-
* @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
|
|
60
|
-
* @returns the url as a valid string
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
private getRequestingUrl;
|
|
68
56
|
/**
|
|
69
57
|
* Raw Request util function
|
|
70
58
|
* @param endpoint endpoint string
|
|
@@ -91,32 +91,6 @@ class LavalinkNode {
|
|
|
91
91
|
this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
|
|
92
92
|
Object.defineProperty(this, Utils_1.NodeSymbol, { configurable: true, value: true });
|
|
93
93
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Parse url params correctly for lavalink requests, including support for urls and uris.
|
|
96
|
-
* @param url input url object
|
|
97
|
-
* @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
|
|
98
|
-
* @returns the url as a valid string
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* ```ts
|
|
102
|
-
* player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
getRequestingUrl(url, extraQueryUrlParams) {
|
|
106
|
-
if (!url.searchParams.size)
|
|
107
|
-
return `${url.origin}${url.pathname}`;
|
|
108
|
-
const keysToAdd = [];
|
|
109
|
-
for (const [paramKey, paramValue] of url.searchParams.entries()) {
|
|
110
|
-
const decoded = decodeURIComponent(paramValue).trim(); // double decoding, once internally, a second time if decoded by provided user.
|
|
111
|
-
if (decoded.includes("://") && !/^https?:\/\//.test(decoded)) { // uri, but not url.
|
|
112
|
-
const [key, ...values] = decoded.split("://");
|
|
113
|
-
keysToAdd.push(`${paramKey}=${encodeURI(`${key}://${encodeURIComponent(values.join("://"))}${extraQueryUrlParams && extraQueryUrlParams?.size > 0 ? `?${extraQueryUrlParams.toString()}` : ""}`)}`);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
keysToAdd.push(`${paramKey}=${encodeURIComponent(decoded)}`);
|
|
117
|
-
}
|
|
118
|
-
return `${url.origin}${url.pathname}?${keysToAdd.join("&")}`;
|
|
119
|
-
}
|
|
120
94
|
/**
|
|
121
95
|
* Raw Request util function
|
|
122
96
|
* @param endpoint endpoint string
|
|
@@ -131,7 +105,7 @@ class LavalinkNode {
|
|
|
131
105
|
*/
|
|
132
106
|
async rawRequest(endpoint, modify) {
|
|
133
107
|
const options = {
|
|
134
|
-
path: `/${this.version}/${endpoint.
|
|
108
|
+
path: `/${this.version}/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`,
|
|
135
109
|
method: "GET",
|
|
136
110
|
headers: {
|
|
137
111
|
"Authorization": this.options.authorization
|
|
@@ -141,7 +115,12 @@ class LavalinkNode {
|
|
|
141
115
|
modify?.(options);
|
|
142
116
|
const url = new URL(`${this.restAddress}${options.path}`);
|
|
143
117
|
url.searchParams.append("trace", "true");
|
|
144
|
-
|
|
118
|
+
if (options.extraQueryUrlParams && options.extraQueryUrlParams?.size > 0) {
|
|
119
|
+
for (const [paramKey, paramValue] of options.extraQueryUrlParams.entries()) {
|
|
120
|
+
url.searchParams.append(paramKey, paramValue);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const urlToUse = url.toString();
|
|
145
124
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
146
125
|
const { path, extraQueryUrlParams, ...fetchOptions } = options; // destructure fetch only options
|
|
147
126
|
const response = await fetch(urlToUse, fetchOptions);
|
|
@@ -180,20 +159,17 @@ class LavalinkNode {
|
|
|
180
159
|
if (["bcsearch", "bandcamp"].includes(Query.source) && !this.info.sourceManagers.includes("bandcamp")) {
|
|
181
160
|
throw new Error("Bandcamp Search only works on the player (lavaplayer version < 2.2.0!");
|
|
182
161
|
}
|
|
183
|
-
|
|
162
|
+
const requestUrl = new URL(`${this.restAddress}/loadtracks`);
|
|
184
163
|
if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
|
|
185
|
-
|
|
186
|
-
uri += url;
|
|
164
|
+
requestUrl.searchParams.append("identifier", Query.query);
|
|
187
165
|
}
|
|
188
166
|
else { // if not make a query out of it
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
uri += `//${encodeURIComponent(Query.query)}`;
|
|
193
|
-
else
|
|
194
|
-
uri += encodeURIComponent(Query.query);
|
|
167
|
+
const fttsPrefix = Query.source === "ftts" ? "//" : "";
|
|
168
|
+
const prefix = Query.source !== "local" ? `${Query.source}:${fttsPrefix}` : "";
|
|
169
|
+
requestUrl.searchParams.append("identifier", `${prefix}${Query.query}`);
|
|
195
170
|
}
|
|
196
|
-
const
|
|
171
|
+
const requestPathAndSearch = requestUrl.pathname + requestUrl.search;
|
|
172
|
+
const res = await this.request(requestPathAndSearch, (options) => {
|
|
197
173
|
if (typeof query === "object" && typeof query.extraQueryUrlParams?.size === "number" && query.extraQueryUrlParams?.size > 0) {
|
|
198
174
|
options.extraQueryUrlParams = query.extraQueryUrlParams;
|
|
199
175
|
}
|
|
@@ -221,7 +197,7 @@ class LavalinkNode {
|
|
|
221
197
|
thumbnail: (res.data.info?.artworkUrl) || (res.data.pluginInfo?.artworkUrl) || ((typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1) ? null : resTracks[res.data?.info?.selectedTrack] ? (resTracks[res.data?.info?.selectedTrack]?.info?.artworkUrl || resTracks[res.data?.info?.selectedTrack]?.info?.pluginInfo?.artworkUrl) : null) || null,
|
|
222
198
|
uri: res.data.info?.url || res.data.info?.uri || res.data.info?.link || res.data.pluginInfo?.url || res.data.pluginInfo?.uri || res.data.pluginInfo?.link || null,
|
|
223
199
|
selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.NodeManager.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
|
|
224
|
-
duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.length || 0), 0) : 0,
|
|
200
|
+
duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || cur?.info?.length || 0), 0) : 0,
|
|
225
201
|
} : null,
|
|
226
202
|
tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
|
|
227
203
|
};
|
|
@@ -1090,7 +1066,7 @@ class LavalinkNode {
|
|
|
1090
1066
|
player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
|
|
1091
1067
|
if (!player.createdTimeStamp && payload.state.time)
|
|
1092
1068
|
player.createdTimeStamp = payload.state.time;
|
|
1093
|
-
if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (0, path_1.isAbsolute)(player.queue.current?.info?.uri))) {
|
|
1069
|
+
if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (player.queue.current?.info?.uri && (0, path_1.isAbsolute)(player.queue.current?.info?.uri)))) {
|
|
1094
1070
|
player.filterManager.filterUpdatedState = false;
|
|
1095
1071
|
if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
|
|
1096
1072
|
this.NodeManager.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerUpdateFilterFixApply, {
|
|
@@ -1295,8 +1271,15 @@ class LavalinkNode {
|
|
|
1295
1271
|
}
|
|
1296
1272
|
this.NodeManager.LavalinkManager.emit("trackStuck", player, track || this.getTrackOfPayload(payload), payload);
|
|
1297
1273
|
// If there are no songs in the queue
|
|
1298
|
-
if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
|
|
1299
|
-
|
|
1274
|
+
if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying"))) {
|
|
1275
|
+
try { //Sometimes the trackStuck event triggers from the Lavalink server, but the track continues playing or resumes after. We explicitly end the track in such cases
|
|
1276
|
+
await player.node.updatePlayer({ guildId: player.guildId, playerOptions: { track: { encoded: null } } }); //trackEnd -> queueEnd
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
catch {
|
|
1280
|
+
return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1300
1283
|
// remove the current track, and enqueue the next one
|
|
1301
1284
|
await (0, Utils_1.queueTrackEnd)(player);
|
|
1302
1285
|
// if no track available, end queue
|
|
@@ -1305,7 +1288,7 @@ class LavalinkNode {
|
|
|
1305
1288
|
}
|
|
1306
1289
|
// play track if autoSkip is true
|
|
1307
1290
|
if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
|
|
1308
|
-
player.play({ noReplace:
|
|
1291
|
+
player.play({ track: player.queue.current, noReplace: false }); // Replace the stuck track with the new track.
|
|
1309
1292
|
}
|
|
1310
1293
|
return;
|
|
1311
1294
|
}
|
|
@@ -1336,22 +1319,22 @@ class LavalinkNode {
|
|
|
1336
1319
|
return;
|
|
1337
1320
|
}
|
|
1338
1321
|
/** @private util function for handling SponsorBlock Segmentloaded event */
|
|
1339
|
-
|
|
1322
|
+
SponsorBlockSegmentLoaded(player, track, payload) {
|
|
1340
1323
|
this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track || this.getTrackOfPayload(payload), payload);
|
|
1341
1324
|
return;
|
|
1342
1325
|
}
|
|
1343
1326
|
/** @private util function for handling SponsorBlock SegmentSkipped event */
|
|
1344
|
-
|
|
1327
|
+
SponsorBlockSegmentSkipped(player, track, payload) {
|
|
1345
1328
|
this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track || this.getTrackOfPayload(payload), payload);
|
|
1346
1329
|
return;
|
|
1347
1330
|
}
|
|
1348
1331
|
/** @private util function for handling SponsorBlock Chaptersloaded event */
|
|
1349
|
-
|
|
1332
|
+
SponsorBlockChaptersLoaded(player, track, payload) {
|
|
1350
1333
|
this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track || this.getTrackOfPayload(payload), payload);
|
|
1351
1334
|
return;
|
|
1352
1335
|
}
|
|
1353
1336
|
/** @private util function for handling SponsorBlock Chaptersstarted event */
|
|
1354
|
-
|
|
1337
|
+
SponsorBlockChapterStarted(player, track, payload) {
|
|
1355
1338
|
this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track || this.getTrackOfPayload(payload), payload);
|
|
1356
1339
|
return;
|
|
1357
1340
|
}
|
|
@@ -1477,7 +1460,8 @@ class LavalinkNode {
|
|
|
1477
1460
|
if (player.queue.current) {
|
|
1478
1461
|
if (payload.type === "TrackEndEvent")
|
|
1479
1462
|
this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
|
|
1480
|
-
|
|
1463
|
+
if (this.NodeManager.LavalinkManager.options.autoSkip)
|
|
1464
|
+
return player.play({ noReplace: true, paused: false });
|
|
1481
1465
|
}
|
|
1482
1466
|
}
|
|
1483
1467
|
else {
|
|
@@ -74,6 +74,9 @@ class Player {
|
|
|
74
74
|
* @param LavalinkManager
|
|
75
75
|
*/
|
|
76
76
|
constructor(options, LavalinkManager) {
|
|
77
|
+
if (typeof options?.customData === "object")
|
|
78
|
+
for (const [key, value] of Object.entries(options.customData))
|
|
79
|
+
this.set(key, value);
|
|
77
80
|
this.options = options;
|
|
78
81
|
this.filterManager = new Filters_1.FilterManager(this);
|
|
79
82
|
this.LavalinkManager = LavalinkManager;
|
|
@@ -267,6 +270,8 @@ class Player {
|
|
|
267
270
|
delete options.clientTrack;
|
|
268
271
|
if (options && "track" in options)
|
|
269
272
|
delete options.track;
|
|
273
|
+
// get rid of the current song without shifting the queue, so that the shifting can happen inside the next .play() call when "autoSkipOnResolveError" is true
|
|
274
|
+
await (0, Utils_1.queueTrackEnd)(this, true);
|
|
270
275
|
// try to play the next track if possible
|
|
271
276
|
if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
|
|
272
277
|
return this.play(options);
|
|
@@ -402,6 +407,8 @@ class Player {
|
|
|
402
407
|
const now = performance.now();
|
|
403
408
|
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
|
|
404
409
|
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
410
|
+
// emit the event
|
|
411
|
+
this.LavalinkManager.emit("playerPaused", this, this.queue.current);
|
|
405
412
|
return this;
|
|
406
413
|
}
|
|
407
414
|
/**
|
|
@@ -414,6 +421,8 @@ class Player {
|
|
|
414
421
|
const now = performance.now();
|
|
415
422
|
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: false } });
|
|
416
423
|
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
424
|
+
// emit the event
|
|
425
|
+
this.LavalinkManager.emit("playerResumed", this, this.queue.current);
|
|
417
426
|
return this;
|
|
418
427
|
}
|
|
419
428
|
/**
|
|
@@ -679,6 +688,7 @@ class Player {
|
|
|
679
688
|
}
|
|
680
689
|
const data = this.toJSON();
|
|
681
690
|
const currentTrack = this.queue.current;
|
|
691
|
+
const segments = await this.getSponsorBlock().catch(() => []);
|
|
682
692
|
const voiceData = this.voice;
|
|
683
693
|
if (!voiceData.endpoint ||
|
|
684
694
|
!voiceData.sessionId ||
|
|
@@ -703,6 +713,33 @@ class Player {
|
|
|
703
713
|
}
|
|
704
714
|
});
|
|
705
715
|
});
|
|
716
|
+
const hasSponsorBlock = this.node.info?.plugins?.find(v => v.name === "sponsorblock-plugin");
|
|
717
|
+
if (hasSponsorBlock) {
|
|
718
|
+
if (segments.length) {
|
|
719
|
+
await this.setSponsorBlock(segments).catch(error => {
|
|
720
|
+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
|
|
721
|
+
this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
|
|
722
|
+
state: "error",
|
|
723
|
+
error: error,
|
|
724
|
+
message: `Player > changeNode() Unable to set SponsorBlock Segments`,
|
|
725
|
+
functionLayer: "Player > changeNode()",
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
await this.setSponsorBlock().catch(error => {
|
|
732
|
+
if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
|
|
733
|
+
this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
|
|
734
|
+
state: "error",
|
|
735
|
+
error: error,
|
|
736
|
+
message: `Player > changeNode() Unable to set SponsorBlock Segments`,
|
|
737
|
+
functionLayer: "Player > changeNode()",
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
706
743
|
if (currentTrack) { // If there is a current track, send it to the new node.
|
|
707
744
|
await this.node.updatePlayer({
|
|
708
745
|
guildId: this.guildId,
|
|
@@ -716,6 +753,8 @@ class Player {
|
|
|
716
753
|
}
|
|
717
754
|
});
|
|
718
755
|
}
|
|
756
|
+
this.paused = data.paused;
|
|
757
|
+
this.playing = data.playing;
|
|
719
758
|
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
720
759
|
return this.node.id;
|
|
721
760
|
}
|
|
@@ -172,6 +172,8 @@ export interface LavalinkManagerEvents {
|
|
|
172
172
|
* @event Manager#LyricsNotFound
|
|
173
173
|
*/
|
|
174
174
|
"LyricsNotFound": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsNotFoundEvent) => void;
|
|
175
|
+
"playerResumed": (player: Player, track: Track | UnresolvedTrack | null) => void;
|
|
176
|
+
"playerPaused": (player: Player, track: Track | UnresolvedTrack | null) => void;
|
|
175
177
|
}
|
|
176
178
|
/**
|
|
177
179
|
* The Bot client Options needed for the manager
|
|
@@ -73,6 +73,8 @@ export interface PlayerOptions {
|
|
|
73
73
|
instaUpdateFiltersFix?: boolean;
|
|
74
74
|
/** If a volume should be applied via filters instead of lavalink-volume */
|
|
75
75
|
applyVolumeAsFilter?: boolean;
|
|
76
|
+
/** Custom Data for the player get/set datastorage */
|
|
77
|
+
customData?: anyObject;
|
|
76
78
|
}
|
|
77
79
|
export type anyObject = {
|
|
78
80
|
[key: string | number]: string | number | null | anyObject;
|
|
@@ -4,7 +4,7 @@ import type { Base64 } from "./Utils.js";
|
|
|
4
4
|
/** Sourcenames provided by lavalink server */
|
|
5
5
|
export type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
|
|
6
6
|
/** Source Names provided by lava src plugin */
|
|
7
|
-
export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts";
|
|
7
|
+
export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz";
|
|
8
8
|
/** Source Names provided by jiosaavan plugin */
|
|
9
9
|
export type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
|
|
10
10
|
/** The SourceNames provided by lavalink */
|
|
@@ -11,7 +11,7 @@ export type Opaque<T, K> = T & {
|
|
|
11
11
|
export type IntegerNumber = Opaque<number, 'Int'>;
|
|
12
12
|
/** Opqaue tyep for floatnumber */
|
|
13
13
|
export type FloatNumber = Opaque<number, 'Float'>;
|
|
14
|
-
export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch";
|
|
14
|
+
export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "dzrec" | "ymsearch" | "ymrec" | "vksearch" | "vkrec" | "tdsearch" | "tdrec" | "qbsearch" | "qbisrc" | "qbrec";
|
|
15
15
|
export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
|
|
16
16
|
export type JioSaavnSearchPlatform = "jssearch" | "jsrec";
|
|
17
17
|
export type DuncteSearchPlatform = "speak" | "phsearch" | "pornhub" | "porn" | "tts";
|
|
@@ -20,9 +20,9 @@ export type LavalinkClientSearchPlatformResolve = "bandcamp" | "bc";
|
|
|
20
20
|
export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "bcsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform | JioSaavnSearchPlatform | LavalinkClientSearchPlatform;
|
|
21
21
|
export type ClientCustomSearchPlatformUtils = "local" | "http" | "https" | "link" | "uri";
|
|
22
22
|
export type ClientSearchPlatform = ClientCustomSearchPlatformUtils | // for file/link requests
|
|
23
|
-
"youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
|
|
23
|
+
"youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "vk" | "vk music" | "vkmusic" | "tidal" | "tidal music" | "qobuz" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn" | "td" | "tidal" | "tdrec";
|
|
24
24
|
export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
|
|
25
|
-
export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "TwitchTv" | "vimeo";
|
|
25
|
+
export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "tidal" | "TwitchTv" | "vimeo";
|
|
26
26
|
export interface PlaylistInfo {
|
|
27
27
|
/** The playlist name */
|
|
28
28
|
name: string;
|
|
@@ -92,13 +92,13 @@ export interface TrackEndEvent extends PlayerEvent {
|
|
|
92
92
|
export interface TrackExceptionEvent extends PlayerEvent {
|
|
93
93
|
type: "TrackExceptionEvent";
|
|
94
94
|
exception?: Exception;
|
|
95
|
-
|
|
95
|
+
track: LavalinkTrack;
|
|
96
96
|
error: string;
|
|
97
97
|
}
|
|
98
98
|
export interface TrackStuckEvent extends PlayerEvent {
|
|
99
99
|
type: "TrackStuckEvent";
|
|
100
100
|
thresholdMs: number;
|
|
101
|
-
|
|
101
|
+
track: LavalinkTrack;
|
|
102
102
|
}
|
|
103
103
|
export interface WebSocketClosedEvent extends PlayerEvent {
|
|
104
104
|
type: "WebSocketClosedEvent";
|
|
@@ -20,7 +20,7 @@ export declare function parseLavalinkConnUrl(connectionUrl: string): {
|
|
|
20
20
|
port: number;
|
|
21
21
|
};
|
|
22
22
|
export declare class ManagerUtils {
|
|
23
|
-
LavalinkManager: LavalinkManager |
|
|
23
|
+
LavalinkManager: LavalinkManager | undefined;
|
|
24
24
|
constructor(LavalinkManager?: LavalinkManager);
|
|
25
25
|
buildPluginInfo(data: any, clientData?: any): any;
|
|
26
26
|
buildTrack(data: LavalinkTrack | Track, requester: unknown): Track;
|
|
@@ -112,4 +112,4 @@ export declare class MiniMap<K, V> extends Map<K, V> {
|
|
|
112
112
|
map<T>(fn: (value: V, key: K, miniMap: this) => T): T[];
|
|
113
113
|
map<This, T>(fn: (this: This, value: V, key: K, miniMap: this) => T, thisArg: This): T[];
|
|
114
114
|
}
|
|
115
|
-
export declare function queueTrackEnd(player: Player): Promise<Track>;
|
|
115
|
+
export declare function queueTrackEnd(player: Player, dontShiftQueue?: boolean): Promise<Track>;
|
|
@@ -30,7 +30,7 @@ function parseLavalinkConnUrl(connectionUrl) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
class ManagerUtils {
|
|
33
|
-
LavalinkManager =
|
|
33
|
+
LavalinkManager = undefined;
|
|
34
34
|
constructor(LavalinkManager) {
|
|
35
35
|
this.LavalinkManager = LavalinkManager;
|
|
36
36
|
}
|
|
@@ -307,6 +307,9 @@ class ManagerUtils {
|
|
|
307
307
|
if (LavalinkManagerStatics_1.SourceLinksRegexes.jiosaavn.test(queryString) && !node.info?.sourceManagers?.includes("jiosaavn")) {
|
|
308
308
|
throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled");
|
|
309
309
|
}
|
|
310
|
+
if (LavalinkManagerStatics_1.SourceLinksRegexes.tidal.test(queryString) && !node.info?.sourceManagers?.includes("tidal")) {
|
|
311
|
+
throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tidal' enabled");
|
|
312
|
+
}
|
|
310
313
|
return;
|
|
311
314
|
}
|
|
312
315
|
transformQuery(query) {
|
|
@@ -371,6 +374,12 @@ class ManagerUtils {
|
|
|
371
374
|
if (source === "speak" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.DuncteBot_Plugin.toLowerCase()))) {
|
|
372
375
|
throw new Error("Lavalink Node has not 'speak' enabled, which is required to have 'speak' work");
|
|
373
376
|
}
|
|
377
|
+
if (source === "tdsearch" && !node.info?.sourceManagers?.includes("tidal")) {
|
|
378
|
+
throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdsearch' work");
|
|
379
|
+
}
|
|
380
|
+
if (source === "tdrec" && !node.info?.sourceManagers?.includes("tidal")) {
|
|
381
|
+
throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdrec' work");
|
|
382
|
+
}
|
|
374
383
|
if (source === "tts" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.GoogleCloudTTS.toLowerCase()))) {
|
|
375
384
|
throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
|
|
376
385
|
}
|
|
@@ -386,6 +395,21 @@ class ManagerUtils {
|
|
|
386
395
|
if (source === "ytsearch" && !node.info?.sourceManagers?.includes("youtube")) {
|
|
387
396
|
throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytsearch' work");
|
|
388
397
|
}
|
|
398
|
+
if (source === "vksearch" && !node.info?.sourceManagers?.includes("vkmusic")) {
|
|
399
|
+
throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vksearch' work");
|
|
400
|
+
}
|
|
401
|
+
if (source === "vkrec" && !node.info?.sourceManagers?.includes("vkmusic")) {
|
|
402
|
+
throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vkrec' work");
|
|
403
|
+
}
|
|
404
|
+
if (source === "qbsearch" && !node.info?.sourceManagers?.includes("qobuz")) {
|
|
405
|
+
throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbsearch' work");
|
|
406
|
+
}
|
|
407
|
+
if (source === "qbisrc" && !node.info?.sourceManagers?.includes("qobuz")) {
|
|
408
|
+
throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbisrc' work");
|
|
409
|
+
}
|
|
410
|
+
if (source === "qbrec" && !node.info?.sourceManagers?.includes("qobuz")) {
|
|
411
|
+
throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbrec' work");
|
|
412
|
+
}
|
|
389
413
|
return;
|
|
390
414
|
}
|
|
391
415
|
}
|
|
@@ -418,7 +442,7 @@ class MiniMap extends Map {
|
|
|
418
442
|
}
|
|
419
443
|
}
|
|
420
444
|
exports.MiniMap = MiniMap;
|
|
421
|
-
async function queueTrackEnd(player) {
|
|
445
|
+
async function queueTrackEnd(player, dontShiftQueue = false) {
|
|
422
446
|
if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
|
|
423
447
|
player.queue.previous.unshift(player.queue.current);
|
|
424
448
|
if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
|
|
@@ -428,10 +452,10 @@ async function queueTrackEnd(player) {
|
|
|
428
452
|
// and if repeatMode == queue, add it back to the queue!
|
|
429
453
|
if (player.repeatMode === "queue" && player.queue.current)
|
|
430
454
|
player.queue.tracks.push(player.queue.current);
|
|
431
|
-
// change the current Track to the next upcoming one
|
|
432
|
-
const nextSong = player.queue.tracks.shift();
|
|
455
|
+
// change the current Track to the next upcoming one
|
|
456
|
+
const nextSong = dontShiftQueue ? null : player.queue.tracks.shift();
|
|
433
457
|
try {
|
|
434
|
-
if (player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
|
|
458
|
+
if (nextSong && player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
|
|
435
459
|
await nextSong.resolve(player);
|
|
436
460
|
player.queue.current = nextSong || null;
|
|
437
461
|
// save it in the DB
|
|
@@ -448,7 +472,7 @@ async function queueTrackEnd(player) {
|
|
|
448
472
|
}
|
|
449
473
|
player.LavalinkManager.emit("trackError", player, player.queue.current, error);
|
|
450
474
|
// try to play the next track if possible
|
|
451
|
-
if (player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
|
|
475
|
+
if (!dontShiftQueue && player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
|
|
452
476
|
return queueTrackEnd(player);
|
|
453
477
|
}
|
|
454
478
|
// return the new current Track
|
|
@@ -5,7 +5,9 @@ export const bandCampSearch = async (player, query, requestUser) => {
|
|
|
5
5
|
console.log(`Lavalink-Client-Debug | SEARCHING | - ${query} on lavalink-client`);
|
|
6
6
|
player.LavalinkManager.utils.validateQueryString(player.node, query);
|
|
7
7
|
try {
|
|
8
|
-
const
|
|
8
|
+
const requestUrl = new URL("https://bandcamp.com/api/nusearch/2/autocomplete");
|
|
9
|
+
requestUrl.searchParams.append("q", query);
|
|
10
|
+
const data = await fetch(requestUrl.toString(), {
|
|
9
11
|
headers: {
|
|
10
12
|
'User-Agent': 'android-async-http/1.4.1 (http://loopj.com/android-async-http)',
|
|
11
13
|
'Cookie': '$Version=1'
|
|
@@ -492,7 +492,7 @@ export class LavalinkManager extends EventEmitter {
|
|
|
492
492
|
functionLayer: "LavalinkManager > sendRawData()",
|
|
493
493
|
});
|
|
494
494
|
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
495
|
-
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function,
|
|
495
|
+
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice });
|
|
496
496
|
}
|
|
497
497
|
else {
|
|
498
498
|
await player.node.updatePlayer({
|
|
@@ -502,8 +502,8 @@ export class LavalinkManager extends EventEmitter {
|
|
|
502
502
|
token: update.token,
|
|
503
503
|
endpoint: update.endpoint,
|
|
504
504
|
sessionId: sessionId2Use,
|
|
505
|
-
}
|
|
506
|
-
}
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
507
|
});
|
|
508
508
|
if (this.options?.advancedOptions?.enableDebugEvents) {
|
|
509
509
|
this.emit("debug", DebugEvents.NoAudioDebug, {
|
|
@@ -513,7 +513,7 @@ export class LavalinkManager extends EventEmitter {
|
|
|
513
513
|
});
|
|
514
514
|
}
|
|
515
515
|
if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
|
|
516
|
-
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function,
|
|
516
|
+
console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
|
|
517
517
|
}
|
|
518
518
|
return;
|
|
519
519
|
}
|