distube 2.8.15 → 3.0.0-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.
Files changed (147) hide show
  1. package/README.md +32 -98
  2. package/dist/DisTube.d.ts +522 -0
  3. package/dist/DisTube.d.ts.map +1 -0
  4. package/dist/DisTube.js +794 -0
  5. package/dist/DisTube.js.map +1 -0
  6. package/dist/constant.d.ts +130 -0
  7. package/dist/constant.d.ts.map +1 -0
  8. package/dist/constant.js +150 -0
  9. package/dist/constant.js.map +1 -0
  10. package/dist/core/DisTubeBase.d.ts +55 -0
  11. package/dist/core/DisTubeBase.d.ts.map +1 -0
  12. package/dist/core/DisTubeBase.js +76 -0
  13. package/dist/core/DisTubeBase.js.map +1 -0
  14. package/dist/core/DisTubeHandler.d.ts +84 -0
  15. package/dist/core/DisTubeHandler.d.ts.map +1 -0
  16. package/dist/core/DisTubeHandler.js +311 -0
  17. package/dist/core/DisTubeHandler.js.map +1 -0
  18. package/dist/core/DisTubeOptions.d.ts +26 -0
  19. package/dist/core/DisTubeOptions.d.ts.map +1 -0
  20. package/dist/core/DisTubeOptions.js +93 -0
  21. package/dist/core/DisTubeOptions.js.map +1 -0
  22. package/dist/core/DisTubeStream.d.ts +52 -0
  23. package/dist/core/DisTubeStream.d.ts.map +1 -0
  24. package/dist/core/DisTubeStream.js +109 -0
  25. package/dist/core/DisTubeStream.js.map +1 -0
  26. package/dist/core/index.d.ts +7 -0
  27. package/dist/core/index.d.ts.map +1 -0
  28. package/dist/core/index.js +19 -0
  29. package/dist/core/index.js.map +1 -0
  30. package/dist/core/manager/BaseManager.d.ts +18 -0
  31. package/dist/core/manager/BaseManager.d.ts.map +1 -0
  32. package/dist/core/manager/BaseManager.js +44 -0
  33. package/dist/core/manager/BaseManager.js.map +1 -0
  34. package/dist/core/manager/QueueManager.d.ts +60 -0
  35. package/dist/core/manager/QueueManager.d.ts.map +1 -0
  36. package/dist/core/manager/QueueManager.js +202 -0
  37. package/dist/core/manager/QueueManager.js.map +1 -0
  38. package/dist/core/manager/index.d.ts +3 -0
  39. package/dist/core/manager/index.d.ts.map +1 -0
  40. package/dist/core/manager/index.js +15 -0
  41. package/dist/core/manager/index.js.map +1 -0
  42. package/dist/core/voice/DJSAdapter.d.ts +4 -0
  43. package/dist/core/voice/DJSAdapter.d.ts.map +1 -0
  44. package/dist/core/voice/DJSAdapter.js +61 -0
  45. package/dist/core/voice/DJSAdapter.js.map +1 -0
  46. package/dist/core/voice/DisTubeVoice.d.ts +83 -0
  47. package/dist/core/voice/DisTubeVoice.d.ts.map +1 -0
  48. package/dist/core/voice/DisTubeVoice.js +236 -0
  49. package/dist/core/voice/DisTubeVoice.js.map +1 -0
  50. package/dist/core/voice/DisTubeVoiceManager.d.ts +41 -0
  51. package/dist/core/voice/DisTubeVoiceManager.d.ts.map +1 -0
  52. package/dist/core/voice/DisTubeVoiceManager.js +67 -0
  53. package/dist/core/voice/DisTubeVoiceManager.js.map +1 -0
  54. package/dist/core/voice/index.d.ts +4 -0
  55. package/dist/core/voice/index.d.ts.map +1 -0
  56. package/dist/core/voice/index.js +16 -0
  57. package/dist/core/voice/index.js.map +1 -0
  58. package/dist/index.d.ts +8 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +23 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/plugin/http.d.ts +8 -0
  63. package/dist/plugin/http.d.ts.map +1 -0
  64. package/dist/plugin/http.js +20 -0
  65. package/dist/plugin/http.js.map +1 -0
  66. package/dist/plugin/https.d.ts +14 -0
  67. package/dist/plugin/https.d.ts.map +1 -0
  68. package/dist/plugin/https.js +50 -0
  69. package/dist/plugin/https.js.map +1 -0
  70. package/dist/plugin/index.d.ts +4 -0
  71. package/dist/plugin/index.d.ts.map +1 -0
  72. package/dist/plugin/index.js +16 -0
  73. package/dist/plugin/index.js.map +1 -0
  74. package/dist/plugin/youtube-dl.d.ts +11 -0
  75. package/dist/plugin/youtube-dl.d.ts.map +1 -0
  76. package/dist/plugin/youtube-dl.js +75 -0
  77. package/dist/plugin/youtube-dl.js.map +1 -0
  78. package/dist/struct/CustomPlugin.d.ts +27 -0
  79. package/dist/struct/CustomPlugin.d.ts.map +1 -0
  80. package/dist/struct/CustomPlugin.js +35 -0
  81. package/dist/struct/CustomPlugin.js.map +1 -0
  82. package/dist/struct/DisTubeError.d.ts +54 -0
  83. package/dist/struct/DisTubeError.d.ts.map +1 -0
  84. package/dist/struct/DisTubeError.js +72 -0
  85. package/dist/struct/DisTubeError.js.map +1 -0
  86. package/dist/struct/ExtractorPlugin.d.ts +29 -0
  87. package/dist/struct/ExtractorPlugin.d.ts.map +1 -0
  88. package/dist/struct/ExtractorPlugin.js +32 -0
  89. package/dist/struct/ExtractorPlugin.js.map +1 -0
  90. package/dist/struct/Playlist.d.ts +42 -0
  91. package/dist/struct/Playlist.d.ts.map +1 -0
  92. package/dist/struct/Playlist.js +104 -0
  93. package/dist/struct/Playlist.js.map +1 -0
  94. package/dist/struct/Plugin.d.ts +82 -0
  95. package/dist/struct/Plugin.d.ts.map +1 -0
  96. package/dist/struct/Plugin.js +108 -0
  97. package/dist/struct/Plugin.js.map +1 -0
  98. package/dist/struct/Queue.d.ts +217 -0
  99. package/dist/struct/Queue.d.ts.map +1 -0
  100. package/dist/struct/Queue.js +480 -0
  101. package/dist/struct/Queue.js.map +1 -0
  102. package/dist/struct/SearchResult.d.ts +28 -0
  103. package/dist/struct/SearchResult.d.ts.map +1 -0
  104. package/dist/struct/SearchResult.js +79 -0
  105. package/dist/struct/SearchResult.js.map +1 -0
  106. package/dist/struct/Song.d.ts +68 -0
  107. package/dist/struct/Song.d.ts.map +1 -0
  108. package/dist/struct/Song.js +229 -0
  109. package/dist/struct/Song.js.map +1 -0
  110. package/dist/struct/TaskQueue.d.ts +33 -0
  111. package/dist/struct/TaskQueue.d.ts.map +1 -0
  112. package/dist/struct/TaskQueue.js +58 -0
  113. package/dist/struct/TaskQueue.js.map +1 -0
  114. package/dist/struct/index.d.ts +10 -0
  115. package/dist/struct/index.d.ts.map +1 -0
  116. package/dist/struct/index.js +22 -0
  117. package/dist/struct/index.js.map +1 -0
  118. package/dist/tsconfig.tsbuildinfo +1 -0
  119. package/dist/type.d.ts +159 -0
  120. package/dist/type.d.ts.map +1 -0
  121. package/dist/type.js +3 -0
  122. package/dist/type.js.map +1 -0
  123. package/dist/util.d.ts +47 -0
  124. package/dist/util.d.ts.map +1 -0
  125. package/dist/util.js +205 -0
  126. package/dist/util.js.map +1 -0
  127. package/package.json +53 -25
  128. package/.eslintrc.json +0 -150
  129. package/.gitattributes +0 -2
  130. package/.github/FUNDING.yml +0 -1
  131. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -17
  132. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  133. package/.github/workflows/npm-publish.yml +0 -17
  134. package/jsdoc.json +0 -97
  135. package/src/DisTube.js +0 -1121
  136. package/src/Playlist.js +0 -107
  137. package/src/Queue.js +0 -120
  138. package/src/SearchResult.js +0 -81
  139. package/src/Song.js +0 -135
  140. package/src/duration.js +0 -39
  141. package/tsconfig.json +0 -24
  142. package/typings/DisTube.d.ts +0 -635
  143. package/typings/Playlist.d.ts +0 -73
  144. package/typings/Queue.d.ts +0 -106
  145. package/typings/SearchResult.d.ts +0 -59
  146. package/typings/Song.d.ts +0 -104
  147. package/typings/duration.d.ts +0 -2
package/src/DisTube.js DELETED
@@ -1,1121 +0,0 @@
1
- const ytdl = require("@distube/ytdl"),
2
- ytsr = require("@distube/ytsr"),
3
- ytpl = require("@distube/ytpl"),
4
- { EventEmitter } = require("events"),
5
- Queue = require("./Queue"),
6
- Song = require("./Song"),
7
- SearchResult = require("./SearchResult"),
8
- Playlist = require("./Playlist"),
9
- Discord = require("discord.js"),
10
- youtube_dl = require("@distube/youtube-dl"),
11
- { promisify } = require("util");
12
- const youtube_dlOptions = ["--no-warnings", "--force-ipv4"];
13
- youtube_dl.getInfo = promisify(youtube_dl.getInfo);
14
-
15
- const isURL = string => {
16
- if (string.includes(" ")) return false;
17
- try {
18
- const url = new URL(string);
19
- if (!["https:", "http:"].includes(url.protocol) ||
20
- url.origin === "null" || !url.host
21
- ) return false;
22
- } catch { return false }
23
- return true;
24
- }
25
- const parseNumber = string => (typeof string === "string" ? Number(string.replace(/\D+/g, "")) : Number(string)) || 0;
26
-
27
- /**
28
- * DisTube options.
29
- * @typedef {object} DisTubeOptions
30
- * @prop {boolean} [emitNewSongOnly=false] `@1.3.0`. If `true`, {@link DisTube#event:playSong} is not emitted when looping a song or next song is the same as the previous one
31
- * @prop {number} [highWaterMark=1<<24] `@2.2.0` ytdl's highWaterMark option.
32
- * @prop {boolean} [leaveOnEmpty=true] Whether or not leaving voice channel if channel is empty in 60s. (Avoid accident leaving)
33
- * @prop {boolean} [leaveOnFinish=false] Whether or not leaving voice channel when the queue ends.
34
- * @prop {boolean} [leaveOnStop=true] Whether or not leaving voice channel after using {@link DisTube#stop|stop()} function.
35
- * @prop {boolean} [searchSongs=false] Whether or not searching for multiple songs to select manually, DisTube will play the first result if `false`
36
- * @prop {string} [youtubeCookie=null] `@2.4.0` YouTube cookies. How to get it: {@link https://github.com/fent/node-ytdl-core/blob/784c04eaf9f3cfac0fe0933155adffe0e2e0848a/example/cookies.js#L6-L12|YTDL's Example}
37
- * @prop {string} [youtubeIdentityToken=null] `@2.4.0` If not given, ytdl-core will try to find it. You can find this by going to a video's watch page, viewing the source, and searching for "ID_TOKEN".
38
- * @prop {boolean} [youtubeDL=true] `@2.8.0` Whether or not using youtube-dl.
39
- * @prop {boolean} [updateYouTubeDL=true] `@2.8.0` Whether or not updating youtube-dl automatically.
40
- * @prop {Object.<string, string>} [customFilters] `@2.7.0` Override or add more ffmpeg filters. Example: `{ "Filter name": "Filter value", "8d": "apulsator=hz=0.075" }`
41
- */
42
- const DisTubeOptions = {
43
- highWaterMark: 1 << 24,
44
- emitNewSongOnly: false,
45
- leaveOnEmpty: true,
46
- leaveOnFinish: false,
47
- leaveOnStop: true,
48
- searchSongs: false,
49
- youtubeCookie: null,
50
- youtubeIdentityToken: null,
51
- youtubeDL: true,
52
- updateYouTubeDL: true,
53
- customFilters: {},
54
- };
55
-
56
- /**
57
- * DisTube audio filters.
58
- * @typedef {("3d"|"bassboost"|"echo"|"karaoke"|"nightcore"|"vaporwave"|"flanger"|"gate"|"haas"|"reverse"|"surround"|"mcompand"|"phaser"|"tremolo"|"earwax"|string)} Filter
59
- * @prop {string} 3d `@2.0.0`
60
- * @prop {string} bassboost `@2.0.0`
61
- * @prop {string} echo `@2.0.0`
62
- * @prop {string} karaoke `@2.0.0`
63
- * @prop {string} nightcore `@2.0.0`
64
- * @prop {string} vaporwave `@2.0.0`
65
- * @prop {string} flanger `@2.4.0`
66
- * @prop {string} gate `@2.4.0`
67
- * @prop {string} haas `@2.4.0`
68
- * @prop {string} reverse `@2.4.0`
69
- * @prop {string} surround `@2.7.0`
70
- * @prop {string} mcompand `@2.7.0`
71
- * @prop {string} phaser `@2.7.0`
72
- * @prop {string} tremolo `@2.7.0`
73
- * @prop {string} earwax `@2.7.0`
74
- */
75
- const ffmpegFilters = {
76
- "3d": "apulsator=hz=0.125",
77
- bassboost: "bass=g=10,dynaudnorm=f=150:g=15",
78
- echo: "aecho=0.8:0.9:1000:0.3",
79
- flanger: "flanger",
80
- gate: "agate",
81
- haas: "haas",
82
- karaoke: "stereotools=mlev=0.1",
83
- nightcore: "asetrate=48000*1.25,aresample=48000,bass=g=5",
84
- reverse: "areverse",
85
- vaporwave: "asetrate=48000*0.8,aresample=48000,atempo=1.1",
86
- mcompand: "mcompand",
87
- phaser: "aphaser",
88
- tremolo: "tremolo",
89
- surround: "surround",
90
- earwax: "earwax",
91
- }
92
-
93
- /**
94
- * Class representing a DisTube.
95
- * @extends EventEmitter
96
- */
97
- class DisTube extends EventEmitter {
98
- /**
99
- * DisTube's current version.
100
- * @type {string}
101
- * @ignore
102
- */
103
- get version() { return require("../package.json").version }
104
- static get version() { return require("../package.json").version }
105
- /**
106
- * Create new DisTube.
107
- * @param {Discord.Client} client Discord.JS client
108
- * @param {DisTubeOptions} [otp={}] Custom DisTube options
109
- * @example
110
- * const Discord = require('discord.js'),
111
- * DisTube = require('distube'),
112
- * client = new Discord.Client();
113
- * // Create a new DisTube
114
- * const distube = new DisTube(client, { searchSongs: true });
115
- * // client.DisTube = distube // make it access easily
116
- * client.login("Your Discord Bot Token")
117
- */
118
- constructor(client, otp = {}) {
119
- super();
120
- if (!client) throw new SyntaxError("Invalid Discord.Client");
121
-
122
- /**
123
- * Discord.JS client
124
- * @type {Discord.Client}
125
- */
126
- this.client = client;
127
-
128
- /**
129
- * Collection of guild queues
130
- * @type {Discord.Collection<string, Queue>}
131
- */
132
- this.guildQueues = new Discord.Collection();
133
-
134
- /**
135
- * DisTube options
136
- * @type {DisTubeOptions}
137
- */
138
- this.options = DisTubeOptions;
139
- Object.assign(this.options, otp);
140
-
141
- /**
142
- * DisTube filters
143
- * @type {Filter}
144
- */
145
- this.filters = ffmpegFilters;
146
- if (typeof otp.customFilters === "object") Object.assign(this.filters, otp.customFilters);
147
-
148
- this.requestOptions = this.options.youtubeCookie ? { headers: { cookie: this.options.youtubeCookie, "x-youtube-identity-token": this.options.youtubeIdentityToken } } : undefined;
149
-
150
- client.on("voiceStateUpdate", (oldState, newState) => {
151
- if (newState && newState.id === client.user.id && !newState.channelID) {
152
- let queue = this.guildQueues.find(gQueue => gQueue.connection && gQueue.connection.channel.id === oldState.channelID);
153
- if (!queue) return;
154
- let guildID = queue.connection.channel.guild.id;
155
- try { this.stop(guildID) } catch { this._deleteQueue(guildID) }
156
- }
157
- if (this.options.leaveOnEmpty && oldState && oldState.channel) {
158
- let queue = this.guildQueues.find(gQueue => gQueue.connection && gQueue.connection.channel.id === oldState.channelID);
159
- if (queue && this._isVoiceChannelEmpty(queue)) {
160
- setTimeout(() => {
161
- let guildID = queue.connection.channel.guild.id;
162
- if (this.guildQueues.has(guildID) && this._isVoiceChannelEmpty(queue)) {
163
- queue.connection.channel.leave();
164
- this.emit("empty", queue.initMessage);
165
- this._deleteQueue(queue.initMessage);
166
- }
167
- }, 60000)
168
- }
169
- }
170
- })
171
-
172
- if (this.options.updateYouTubeDL) {
173
- require("@distube/youtube-dl/lib/downloader")()
174
- .then(message => console.log(`[DisTube] ${message}`))
175
- .catch(console.error)
176
- .catch(() => console.log("[DisTube] Unable to update youtube-dl, using default version."));
177
- }
178
- }
179
-
180
- /**
181
- * Resolve a Song
182
- * @async
183
- * @param {Discord.Message} message The message from guild channel
184
- * @param {string|Song} song Youtube url | Search string | {@link Song}
185
- * @private
186
- * @ignore
187
- * @returns {Promise<Song|Song[]>} Resolved Song
188
- */
189
- async _resolveSong(message, song) {
190
- if (!song) return null;
191
- if (song instanceof Song) return song;
192
- if (song instanceof SearchResult) return new Song(await ytdl.getInfo(song.url, { requestOptions: this.requestOptions }), message.author, true);
193
- if (typeof song === "object") return new Song(song, message.author);
194
- if (ytdl.validateURL(song)) return new Song(await ytdl.getInfo(song, { requestOptions: this.requestOptions }), message.author, true);
195
- if (isURL(song)) {
196
- if (!this.options.youtubeDL) throw new Error("Not Supported URL!");
197
- let info = await youtube_dl.getInfo(song, youtube_dlOptions).catch(e => { throw new Error(`[youtube-dl] ${e.stderr || e}`) });
198
- if (Array.isArray(info) && info.length > 0) return info.map(i => new Song(i, message.author));
199
- return new Song(info, message.author);
200
- }
201
- return this._resolveSong(message, await this._searchSong(message, song));
202
- }
203
-
204
- /**
205
- * Handle a Song or an array of Song
206
- * @async
207
- * @param {Discord.Message} message The message from guild channel
208
- * @param {Song|SearchResult} song {@link Song} | {@link SearchResult}
209
- * @private
210
- * @ignore
211
- */
212
- async _handleSong(message, song, skip = false) {
213
- if (!song) return;
214
- if (Array.isArray(song)) this._handlePlaylist(message, song, skip);
215
- else if (this.getQueue(message)) {
216
- let queue = this._addToQueue(message, song, skip);
217
- if (skip) this.skip(message);
218
- else this.emit("addSong", message, queue, song);
219
- } else {
220
- let queue = await this._newQueue(message, song);
221
- this.emit("playSong", message, queue, song);
222
- }
223
- }
224
-
225
- /**
226
- * Play / add a song or playlist from url. Search and play a song if it is not a valid url.
227
- * @async
228
- * @param {Discord.Message} message The message from guild channel
229
- * @param {string|Song|SearchResult} song Youtube url | Search string | {@link Song} | {@link SearchResult}
230
- * @example
231
- * client.on('message', (message) => {
232
- * if (!message.content.startsWith(config.prefix)) return;
233
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
234
- * const command = args.shift();
235
- * if (command == "play")
236
- * distube.play(message, args.join(" "));
237
- * });
238
- */
239
- async play(message, song) {
240
- if (!song) return;
241
- try {
242
- if (ytpl.validateID(song)) await this._handlePlaylist(message, song);
243
- else await this._handleSong(message, await this._resolveSong(message, song));
244
- } catch (e) {
245
- e.message = `play(${song}) encountered:\n${e.message}`;
246
- this._emitError(message, e);
247
- }
248
- }
249
-
250
- /**
251
- * `@2.0.0` Skip the playing song and play a song or playlist
252
- * @async
253
- * @param {Discord.Message} message The message from guild channel
254
- * @param {string|Song|SearchResult} song Youtube url | Search string | {@link Song} | {@link SearchResult}
255
- * @example
256
- * client.on('message', (message) => {
257
- * if (!message.content.startsWith(config.prefix)) return;
258
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
259
- * const command = args.shift();
260
- * if (command == "playSkip")
261
- * distube.playSkip(message, args.join(" "));
262
- * });
263
- */
264
- async playSkip(message, song) {
265
- if (!song) return;
266
- try {
267
- if (ytpl.validateID(song)) await this._handlePlaylist(message, song, true);
268
- else await this._handleSong(message, await this._resolveSong(message, song), true);
269
- } catch (e) {
270
- e.message = `playSkip(${song}) encountered:\n${e.message}`;
271
- this._emitError(message, e);
272
- }
273
- }
274
-
275
- /**
276
- * `@2.1.0` Play or add array of Youtube video urls.
277
- * {@link DisTube#event:playList} or {@link DisTube#event:addList} will be emitted
278
- * with `playlist`'s properties include `properties` parameter's properties such as
279
- * `user`, `songs`, `duration`, `formattedDuration`, `thumbnail` like {@link Playlist}
280
- * @async
281
- * @param {Discord.Message} message The message from guild channel
282
- * @param {string[]} urls Array of Youtube url
283
- * @param {Object} [properties={}] Additional properties such as `name`
284
- * @param {boolean} [playSkip=false] Whether or not play this playlist instantly
285
- * @example
286
- * let songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
287
- * distube.playCustomPlaylist(message, songs, { name: "My playlist name" });
288
- */
289
- async playCustomPlaylist(message, urls, properties = {}, playSkip = false) {
290
- if (!urls.length) return;
291
- try {
292
- let songs = urls.filter(url => isURL(url)).map(url => this._resolveSong(message, url).catch(() => { }));
293
- songs = (await Promise.all(songs)).filter(song => song);
294
- let playlist = new Playlist(songs, message.author, properties);
295
- await this._handlePlaylist(message, playlist, playSkip);
296
- } catch (e) {
297
- this._emitError(message, e);
298
- }
299
- }
300
-
301
- /**
302
- * PLay / add a playlist
303
- * @async
304
- * @private
305
- * @ignore
306
- * @param {Discord.Message} message The message from guild channel
307
- * @param {string|Song[]|Playlist} arg2 Youtube playlist url | a Playlist
308
- * @param {boolean} skip Skip the current song
309
- */
310
- async _handlePlaylist(message, arg2, skip = false) {
311
- let playlist;
312
- if (typeof arg2 === "object") playlist = arg2; // Song[] or Playlist
313
- else if (typeof arg2 === "string") {
314
- playlist = await ytpl(arg2, { limit: Infinity });
315
- playlist.items = playlist.items.filter(v => !v.thumbnail.includes("no_thumbnail")).map(v => new Song(v, message.author, true));
316
- }
317
- if (!playlist) throw Error("Invalid Playlist");
318
- if (!(playlist instanceof Playlist)) playlist = new Playlist(playlist, message.author)
319
- if (!playlist.songs.length) throw Error("No valid video in the playlist");
320
- let songs = playlist.songs;
321
- let queue = this.getQueue(message);
322
- if (queue) {
323
- this._addSongsToQueue(message, songs, skip);
324
- if (skip) this.skip(message);
325
- else this.emit("addList", message, queue, playlist);
326
- } else {
327
- let song = songs.shift();
328
- queue = await this._newQueue(message, song);
329
- if (songs.length) this._addSongsToQueue(message, songs);
330
- songs.unshift(song);
331
- this.emit("playList", message, queue, playlist, queue.songs[0]);
332
- }
333
- }
334
-
335
- /**
336
- * `@2.0.0` Search for a song. You can customize how user answers instead of send a number.
337
- * Then use {@link DisTube#play|play(message, aResultFromSearch)} or {@link DisTube#playSkip|playSkip()} to play it.
338
- * @async
339
- * @param {string} string The string search for
340
- * @throws {NotFound} If not found
341
- * @throws {Error} If an error encountered
342
- * @returns {Promise<SearchResult[]>} Array of results
343
- */
344
- async search(string, retried = false) {
345
- try {
346
- let search = await ytsr(string, { limit: 15 });
347
- let results = search.items.map(i => new SearchResult(i));
348
- if (results.length === 0) throw Error("No result!");
349
- return results;
350
- } catch (e) {
351
- if (retried) throw e;
352
- return this.search(string, true);
353
- }
354
- }
355
-
356
- /**
357
- * Search for a song, fire {@link DisTube#event:error} if not found.
358
- * @async
359
- * @private
360
- * @ignore
361
- * @param {Discord.Message} message The message from guild channel
362
- * @param {string} name The string search for
363
- * @returns {Song} Song info
364
- */
365
- async _searchSong(message, name) {
366
- let results = await this.search(name);
367
- let result = results[0];
368
- if (this.options.searchSongs) {
369
- this.emit("searchResult", message, results);
370
- try {
371
- let answers = await message.channel.awaitMessages(m => m.author.id === message.author.id, {
372
- max: 1,
373
- time: 60000,
374
- errors: ["time"],
375
- })
376
- if (!answers.first()) throw new Error();
377
- let index = parseInt(answers.first().content, 10);
378
- if (isNaN(index) || index > results.length || index < 1) throw new Error();
379
- result = results[index - 1];
380
- } catch {
381
- this.emit("searchCancel", message);
382
- return null;
383
- }
384
- }
385
- return result;
386
- }
387
-
388
- /**
389
- * Create a new guild queue
390
- * @async
391
- * @private
392
- * @ignore
393
- * @param {Discord.Message} message The message from guild channel
394
- * @param {Song} song Song to play
395
- * @throws {NotInVoice} if user not in a voice channel
396
- * @returns {Promise<Queue>}
397
- */
398
- async _newQueue(message, song, retried = false) {
399
- let voice = message.member.voice.channel;
400
- if (!voice) throw new Error("User is not in the voice channel.");
401
- let queue = new Queue(message, song);
402
- this.emit("initQueue", queue);
403
- this.guildQueues.set(message.guild.id, queue);
404
- try {
405
- queue.connection = await voice.join();
406
- } catch (e) {
407
- this._deleteQueue(message);
408
- e.message = `DisTube cannot join the voice channel!\nReason: ${e.message}`;
409
- if (retried) throw e;
410
- return this._newQueue(message, song, true);
411
- }
412
- queue.connection.on("error", e => {
413
- e.message = `There is a problem with Discord Voice Connection.\nPlease try again! Sorry for the interruption!\nReason: ${e.message}`;
414
- this._emitError(message, e);
415
- this._deleteQueue(message);
416
- })
417
- await this._playSong(message);
418
- return queue;
419
- }
420
-
421
- /**
422
- * Delete a guild queue
423
- * @private
424
- * @ignore
425
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
426
- */
427
- _deleteQueue(message) {
428
- let queue = this.getQueue(message);
429
- if (!queue) return;
430
- if (queue.dispatcher) try { queue.dispatcher.destroy() } catch { }
431
- if (queue.stream) try { queue.stream.destroy() } catch { }
432
- if (typeof message === "string") this.guildQueues.delete(message);
433
- else if (message && message.guild) this.guildQueues.delete(message.guild.id);
434
- }
435
-
436
- /**
437
- * Get the guild queue
438
- * @param {Discord.Snowflake|Discord.Message} message The guild ID or message from guild channel.
439
- * @returns {Queue} The guild queue
440
- * @example
441
- * client.on('message', (message) => {
442
- * if (!message.content.startsWith(config.prefix)) return;
443
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
444
- * const command = args.shift();
445
- * if (command == "queue") {
446
- * let queue = distube.getQueue(message);
447
- * message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
448
- * `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
449
- * ).join("\n"));
450
- * }
451
- * });
452
- */
453
- getQueue(message) {
454
- if (typeof message === "string") return this.guildQueues.get(message);
455
- if (!message || !message.guild) throw TypeError("Parameter should be Discord.Message or server ID!");
456
- return this.guildQueues.get(message.guild.id);
457
- }
458
-
459
- /**
460
- * Add a video to queue
461
- * @private
462
- * @ignore
463
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
464
- * @param {Song} song Song to add
465
- * @param {boolean} [unshift=false] Unshift
466
- * @throws {NotInVoice} if result is empty
467
- * @returns {Queue}
468
- */
469
- _addToQueue(message, song, unshift = false) {
470
- let queue = this.getQueue(message);
471
- if (!queue) throw new Error("NotPlaying");
472
- if (!song) throw new Error("NoSong");
473
- if (unshift) {
474
- let playing = queue.songs.shift();
475
- queue.songs.unshift(playing, song);
476
- } else { queue.songs.push(song); }
477
- return queue;
478
- }
479
-
480
- /**
481
- * Add a array of videos to queue
482
- * @private
483
- * @ignore
484
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
485
- * @param {Song[]} songs Array of song to add
486
- * @param {boolean} [unshift=false] Unshift
487
- * @returns {Queue}
488
- */
489
- _addSongsToQueue(message, songs, unshift = false) {
490
- let queue = this.getQueue(message);
491
- if (!queue) throw new Error("NotPlaying");
492
- if (!songs.length) throw new Error("NoSong");
493
- if (unshift) {
494
- let playing = queue.songs.shift();
495
- queue.songs.unshift(playing, ...songs);
496
- } else { queue.songs.push(...songs); }
497
- return queue;
498
- }
499
-
500
- /**
501
- * Pause the guild stream
502
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
503
- * @returns {Queue} The guild queue
504
- * @throws {NotPlaying} No playing queue
505
- */
506
- pause(message) {
507
- let queue = this.getQueue(message);
508
- if (!queue) throw new Error("NotPlaying");
509
- queue.playing = false;
510
- queue.pause = true;
511
- queue.dispatcher.pause();
512
- return queue;
513
- }
514
-
515
- /**
516
- * Resume the guild stream
517
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
518
- * @returns {Queue} The guild queue
519
- * @throws {NotPlaying} No playing queue
520
- */
521
- resume(message) {
522
- let queue = this.getQueue(message);
523
- if (!queue) throw new Error("NotPlaying");
524
- queue.playing = true;
525
- queue.pause = false;
526
- queue.dispatcher.resume();
527
- return queue;
528
- }
529
-
530
- /**
531
- * Stop the guild stream
532
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
533
- * @throws {NotPlaying} No playing queue
534
- * @example
535
- * client.on('message', (message) => {
536
- * if (!message.content.startsWith(config.prefix)) return;
537
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
538
- * const command = args.shift();
539
- * if (command == "stop") {
540
- * distube.stop(message);
541
- * message.channel.send("Stopped the queue!");
542
- * }
543
- * });
544
- */
545
- stop(message) {
546
- let queue = this.getQueue(message);
547
- if (!queue) throw new Error("NotPlaying");
548
- queue.stopped = true;
549
- if (queue.dispatcher) try { queue.dispatcher.end() } catch { }
550
- if (this.options.leaveOnStop && queue.connection) try { queue.connection.channel.leave() } catch { }
551
- this._deleteQueue(message);
552
- }
553
-
554
- /**
555
- * Set the guild stream's volume
556
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
557
- * @param {number} percent The percentage of volume you want to set
558
- * @returns {Queue} The guild queue
559
- * @throws {NotPlaying} No playing queue
560
- * @example
561
- * client.on('message', (message) => {
562
- * if (!message.content.startsWith(config.prefix)) return;
563
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
564
- * const command = args.shift();
565
- * if (command == "volume")
566
- * distube.setVolume(message, args[0]);
567
- * });
568
- */
569
- setVolume(message, percent) {
570
- let queue = this.getQueue(message);
571
- if (!queue) throw new Error("NotPlaying");
572
- queue.volume = percent;
573
- queue.dispatcher.setVolume(queue.volume / 100);
574
- return queue
575
- }
576
-
577
- /**
578
- * Skip the playing song
579
- *
580
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
581
- * @returns {Queue} The guild queue
582
- * @throws {NotPlaying} No playing queue
583
- * @throws {NoSong} if there is no song in queue
584
- * @example
585
- * client.on('message', (message) => {
586
- * if (!message.content.startsWith(config.prefix)) return;
587
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
588
- * const command = args.shift();
589
- * if (command == "skip")
590
- * distube.skip(message);
591
- * });
592
- */
593
- skip(message) {
594
- let queue = this.getQueue(message);
595
- if (!queue) throw new Error("NotPlaying");
596
- if (queue.songs <= 1 && !queue.autoplay) throw new Error("NoSong");
597
- queue.skipped = true;
598
- queue.dispatcher.end();
599
- return queue;
600
- }
601
-
602
- /**
603
- * Shuffle the guild queue songs
604
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
605
- * @returns {Queue} The guild queue
606
- * @example
607
- * client.on('message', (message) => {
608
- * if (!message.content.startsWith(config.prefix)) return;
609
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
610
- * const command = args.shift();
611
- * if (command == "shuffle")
612
- * distube.shuffle(message);
613
- * });
614
- */
615
- shuffle(message) {
616
- let queue = this.getQueue(message);
617
- if (!queue) throw new Error("NotPlaying");
618
- let playing = queue.songs.shift();
619
- for (let i = queue.songs.length - 1; i > 0; i--) {
620
- let j = Math.floor(Math.random() * (i + 1));
621
- [queue.songs[i], queue.songs[j]] = [queue.songs[j], queue.songs[i]];
622
- }
623
- queue.songs.unshift(playing);
624
- return queue;
625
- }
626
-
627
- /**
628
- * Jump to the song number in the queue.
629
- * The next one is 1,...
630
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
631
- * @param {number} num The song number to play
632
- * @returns {Queue} The guild queue
633
- * @throws {InvalidSong} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
634
- * @example
635
- * client.on('message', (message) => {
636
- * if (!message.content.startsWith(config.prefix)) return;
637
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
638
- * const command = args.shift();
639
- * if (command == "jump")
640
- * distube.jump(message, parseInt(args[0]))
641
- * .catch(err => message.channel.send("Invalid song number."));
642
- * });
643
- */
644
- jump(message, num) {
645
- let queue = this.getQueue(message);
646
- if (!queue) throw new Error("NotPlaying");
647
- if (num > queue.songs.length || num < 1) throw new Error("InvalidSong");
648
- queue.songs = queue.songs.splice(num - 1);
649
- queue.skipped = true;
650
- if (queue.dispatcher) queue.dispatcher.end();
651
- return queue;
652
- }
653
-
654
- /**
655
- * Set the repeat mode of the guild queue.
656
- * Turn off if repeat mode is the same value as new mode.
657
- * Toggle mode: `mode = null` `(0 -> 1 -> 2 -> 0...)`
658
- *
659
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
660
- * @param {number} mode The repeat modes `(0: disabled, 1: Repeat a song, 2: Repeat all the queue)`
661
- * @returns {number} The new repeat mode
662
- *
663
- * @example
664
- * client.on('message', (message) => {
665
- * if (!message.content.startsWith(config.prefix)) return;
666
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
667
- * const command = args.shift();
668
- * if (command == "repeat") {
669
- * let mode = distube.setRepeatMode(message, parseInt(args[0]));
670
- * mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
671
- * message.channel.send("Set repeat mode to `" + mode + "`");
672
- * }
673
- * });
674
- */
675
- setRepeatMode(message, mode = null) {
676
- let queue = this.getQueue(message);
677
- if (!queue) throw new Error("NotPlaying");
678
- mode = parseInt(mode, 10);
679
- if (!mode && mode !== 0) queue.repeatMode = (queue.repeatMode + 1) % 3;
680
- else if (queue.repeatMode === mode) queue.repeatMode = 0;
681
- else queue.repeatMode = mode;
682
- return queue.repeatMode;
683
- }
684
-
685
- /**
686
- * Toggle autoplay mode
687
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
688
- * @returns {boolean} Autoplay mode state
689
- * @throws {NotPlaying} No playing queue
690
- * @example
691
- * client.on('message', (message) => {
692
- * if (!message.content.startsWith(config.prefix)) return;
693
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
694
- * const command = args.shift();
695
- * if (command == "autoplay") {
696
- * let mode = distube.toggleAutoplay(message);
697
- * message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
698
- * }
699
- * });
700
- */
701
- toggleAutoplay(message) {
702
- let queue = this.getQueue(message);
703
- if (!queue) throw new Error("NotPlaying");
704
- queue.autoplay = !queue.autoplay;
705
- return queue.autoplay;
706
- }
707
-
708
- /**
709
- * Whether or not a guild is playing music.
710
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel to check
711
- * @returns {boolean} Whether or not the guild is playing song(s)
712
- */
713
- isPlaying(message) {
714
- let queue = this.getQueue(message);
715
- return queue ? queue.playing || !queue.pause : false;
716
- }
717
-
718
- /**
719
- * Whether or not the guild queue is paused
720
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel to check
721
- * @returns {boolean} Whether or not the guild queue is paused
722
- */
723
- isPaused(message) {
724
- let queue = this.getQueue(message);
725
- return queue ? queue.pause : false;
726
- }
727
-
728
- /**
729
- * Whether or not the queue's voice channel is empty
730
- * @private
731
- * @ignore
732
- * @param {Queue} queue The guild queue
733
- * @returns {boolean} No user in voice channel return `true`
734
- */
735
- _isVoiceChannelEmpty(queue) {
736
- let voiceChannel = queue.connection.channel;
737
- let members = voiceChannel.members.filter(m => !m.user.bot);
738
- return !members.size;
739
- }
740
-
741
- /**
742
- * TODO: Remove this
743
- * @deprecated use {@link DisTube#addRelatedVideo} instead
744
- * @param {DisTube.Message} message Message
745
- * @returns {Promise<Queue>}
746
- */
747
- runAutoplay(message) {
748
- console.warn(`\`DisTube#runAutoplay\` is deprecated, use \`DisTube#addRelatedVideo\` instead.`);
749
- return this.addRelatedVideo(message);
750
- }
751
-
752
- /**
753
- * Add related song to the queue
754
- * @async
755
- * @param {Discord.Snowflake|Discord.Message} message The message from guild channel
756
- * @returns {Promise<Queue>} The guild queue
757
- */
758
- async addRelatedVideo(message) {
759
- let queue = this.getQueue(message);
760
- if (!queue) throw new Error("NotPlaying");
761
- let song = queue.songs[0];
762
- if (!song.youtube) {
763
- this.emit("noRelated", message);
764
- return queue;
765
- }
766
- let related = song.related;
767
- if (!Array.isArray(related)) related = (await ytdl.getBasicInfo(song.url, { requestOptions: this.requestOptions })).related_videos;
768
- if (Array.isArray(related)) {
769
- const relatedVideo = related.find(s => !queue.previousSongs.includes(s.id));
770
- if (!relatedVideo && !relatedVideo.id) {
771
- this.emit("noRelated", message);
772
- return queue;
773
- }
774
- this._addToQueue(message, new Song(await ytdl.getInfo(relatedVideo.id, { requestOptions: this.requestOptions }), this.client.user, true));
775
- } else this.emit("noRelated", message);
776
- return queue;
777
- }
778
-
779
- /**
780
- * `@2.0.0` Enable or disable a filter of the queue, replay the playing song.
781
- * Available filters: {@link Filter}
782
- *
783
- * @param {Discord.Message} message The message from guild channel
784
- * @param {Filter} filter A filter name
785
- * @returns {string} Current queue's filter name.
786
- * @example
787
- * client.on('message', (message) => {
788
- * if (!message.content.startsWith(config.prefix)) return;
789
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
790
- * const command = args.shift();
791
- * if ([`3d`, `bassboost`, `echo`, `karaoke`, `nightcore`, `vaporwave`].includes(command)) {
792
- * let filter = distube.setFilter(message, command);
793
- * message.channel.send("Current queue filter: " + (filter || "Off"));
794
- * }
795
- * });
796
- */
797
- setFilter(message, filter) {
798
- let queue = this.getQueue(message);
799
- if (!queue) throw new Error("NotPlaying");
800
- if (!Object.prototype.hasOwnProperty.call(this.filters, filter)) throw new TypeError(`${filter} is not a Filter (https://DisTube.js.org/global.html#Filter).`);
801
- if (queue.filter === filter) queue.filter = null;
802
- else queue.filter = filter;
803
- queue.beginTime = queue.currentTime;
804
- this._playSong(message);
805
- return queue.filter;
806
- }
807
-
808
- /**
809
- * `@2.7.0` Set the playing time to another position
810
- *
811
- * @param {Discord.Message} message The message from guild channel
812
- * @param {number} time Time in milliseconds
813
- * @example
814
- * client.on('message', message => {
815
- * if (!message.content.startsWith(config.prefix)) return;
816
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
817
- * const command = args.shift();
818
- * if (command = 'seek')
819
- * distube.seek(message, Number(args[0]));
820
- * });
821
- */
822
- seek(message, time) {
823
- let queue = this.getQueue(message);
824
- if (!queue) throw new Error("NotPlaying");
825
- queue.beginTime = time;
826
- this._playSong(message);
827
- }
828
-
829
- /**
830
- * Emit error event
831
- * @private
832
- * @ignore
833
- */
834
- _emitError(message, error) {
835
- if (this.listeners("error").length) this.emit("error", message, error);
836
- else this.emit("error", error);
837
- }
838
-
839
- /**
840
- * Whether or not emit playSong event
841
- * @private
842
- * @ignore
843
- */
844
- _emitPlaySong(queue) {
845
- if (
846
- !this.options.emitNewSongOnly ||
847
- (
848
- queue.repeatMode !== 1 &&
849
- (!queue.songs[1] || queue.songs[0].id !== queue.songs[1].id)
850
- )
851
- ) return true;
852
- return false;
853
- }
854
-
855
- /**
856
- * Create a ytdl stream
857
- * @private
858
- * @ignore
859
- */
860
- _createStream(queue) {
861
- let song = queue.songs[0];
862
- let encoderArgs = queue.filter ? ["-af", this.filters[queue.filter]] : null;
863
- let streamOptions = {
864
- opusEncoded: true,
865
- filter: song.isLive ? "audioandvideo" : "audioonly",
866
- quality: "highestaudio",
867
- highWaterMark: this.options.highWaterMark,
868
- requestOptions: this.requestOptions,
869
- encoderArgs,
870
- seek: queue.beginTime / 1000,
871
- };
872
- if (song.youtube) return ytdl(song.info, streamOptions);
873
- return ytdl.arbitraryStream(song.streamURL, streamOptions);
874
- }
875
-
876
- /**
877
- * Play a song on voice connection
878
- * @private
879
- * @ignore
880
- * @param {Discord.Message} message The message from guild channel
881
- */
882
- async _playSong(message) {
883
- let queue = this.getQueue(message);
884
- if (!queue) return;
885
- if (!queue.songs.length) {
886
- this._deleteQueue(message);
887
- return;
888
- }
889
- let song = queue.songs[0];
890
- try {
891
- let errorEmitted = false;
892
- // Queue.stream.on('info') should works but maybe DisTube#playSong will emit before ytdl#info
893
- if (song.youtube && !song.info) {
894
- let { videoDetails } = song.info = await ytdl.getInfo(song.url, { requestOptions: this.requestOptions });
895
- song.views = parseNumber(videoDetails.viewCount);
896
- song.likes = parseNumber(videoDetails.likes);
897
- song.dislikes = parseNumber(videoDetails.dislikes);
898
- if (song.info.formats.length) {
899
- song.streamURL = ytdl.chooseFormat(song.info.formats, {
900
- filter: song.isLive ? "audioandvideo" : "audioonly",
901
- quality: "highestaudio",
902
- }).url;
903
- }
904
- }
905
- let stream = this._createStream(queue).on("error", e => {
906
- errorEmitted = true;
907
- e.message = `${e.message}\nID: ${song.id}\nName: ${song.name}`;
908
- this._emitError(message, e);
909
- });
910
- queue.dispatcher = queue.connection.play(stream, {
911
- highWaterMark: 1,
912
- type: "opus",
913
- volume: queue.volume / 100,
914
- bitrate: "auto",
915
- }).on("finish", () => this._handleSongFinish(message, queue))
916
- .on("error", e => {
917
- this._handlePlayingError(message, queue, errorEmitted ? null : e);
918
- });
919
- if (queue.stream) queue.stream.destroy();
920
- queue.stream = stream;
921
- } catch (e) {
922
- this._handlePlayingError(message, queue, e);
923
- }
924
- }
925
-
926
- /**
927
- * Handle the queue when a Song finish
928
- * @private
929
- * @ignore
930
- * @param {Discord.Message} message message
931
- * @param {Queue} queue queue
932
- */
933
- async _handleSongFinish(message, queue) {
934
- if (queue.stopped) return;
935
- if (this.options.leaveOnEmpty && this._isVoiceChannelEmpty(queue)) {
936
- this._deleteQueue(message);
937
- queue.connection.channel.leave();
938
- this.emit("empty", message);
939
- return;
940
- }
941
- if (queue.repeatMode === 2 && !queue.skipped) queue.songs.push(queue.songs[0]);
942
- if (queue.songs.length <= 1 && (queue.skipped || !queue.repeatMode)) {
943
- if (queue.autoplay) try { await this.addRelatedVideo(message) } catch { this.emit("noRelated", message) }
944
- if (queue.songs.length <= 1) {
945
- this._deleteQueue(message);
946
- if (this.options.leaveOnFinish && !queue.stopped) queue.connection.channel.leave();
947
- if (!queue.autoplay) this.emit("finish", message);
948
- return;
949
- }
950
- }
951
- const emitSong = this._emitPlaySong(queue);
952
- if (queue.repeatMode !== 1 || queue.skipped) {
953
- const { id } = queue.songs.shift();
954
- queue.previousSongs.push(id);
955
- }
956
- queue.skipped = false;
957
- queue.beginTime = 0;
958
- await this._playSong(message);
959
- if (emitSong) this.emit("playSong", message, queue, queue.songs[0]);
960
- }
961
-
962
- /**
963
- * Handle error while playing
964
- * @private
965
- * @ignore
966
- * @param {Discord.Message} message message
967
- * @param {Queue} queue queue
968
- * @param {Error} error error
969
- */
970
- _handlePlayingError(message, queue, error = null) {
971
- let song = queue.songs.shift();
972
- if (error) {
973
- error.message = `${error.message}\nID: ${song.id}\nName: ${song.name}`;
974
- this._emitError(message, error);
975
- }
976
- if (queue.songs.length > 0) this._playSong(message).then(() => this.emit("playSong", message, queue, queue.songs[0]));
977
- else try { this.stop(message) } catch { this._deleteQueue(message) }
978
- }
979
- }
980
-
981
- module.exports = DisTube;
982
-
983
- /**
984
- * Emitted after DisTube add playlist to guild queue
985
- *
986
- * @event DisTube#addList
987
- * @param {Discord.Message} message The message from guild channel
988
- * @param {Queue} queue The guild queue
989
- * @param {Playlist} playlist Playlist info
990
- * @since 1.1.0
991
- * @example
992
- * const status = (queue) => `Volume: \`${queue.volume}%\` | Loop: \`${queue.repeatMode ? queue.repeatMode == 2 ? "Server Queue" : "This Song" : "Off"}\` | Autoplay: \`${queue.autoplay ? "On" : "Off"}\``;
993
- * distube.on("addList", (message, queue, playlist) => message.channel.send(
994
- * `Added \`${playlist.name}\` playlist (${playlist.songs.length} songs) to queue\n${status(queue)}`
995
- * ));
996
- */
997
-
998
- /**
999
- * Emitted after DisTube add new song to guild queue
1000
- *
1001
- * @event DisTube#addSong
1002
- * @param {Discord.Message} message The message from guild channel
1003
- * @param {Queue} queue The guild queue
1004
- * @param {Song} song Added song
1005
- * @example
1006
- * const status = (queue) => `Volume: \`${queue.volume}%\` | Loop: \`${queue.repeatMode ? queue.repeatMode == 2 ? "Server Queue" : "This Song" : "Off"}\` | Autoplay: \`${queue.autoplay ? "On" : "Off"}\``;
1007
- * distube.on("addSong", (message, queue, song) => message.channel.send(
1008
- * `Added ${song.name} - \`${song.formattedDuration}\` to the queue by ${song.user}`
1009
- * ));
1010
- */
1011
-
1012
- /**
1013
- * Emitted when there is no user in VoiceChannel and {@link DisTubeOptions}.leaveOnEmpty is `true`.
1014
- *
1015
- * @event DisTube#empty
1016
- * @param {Discord.Message} message The message from guild channel
1017
- * @example
1018
- * distube.on("empty", message => message.channel.send("Channel is empty. Leaving the channel"))
1019
- */
1020
-
1021
- /**
1022
- * Emitted when {@link DisTube} encounters an error.
1023
- *
1024
- * @event DisTube#error
1025
- * @param {Discord.Message} message The message from guild channel
1026
- * @param {Error} err The error encountered
1027
- * @example
1028
- * distube.on("error", (message, err) => message.channel.send(
1029
- * "An error encountered: " + err
1030
- * ));
1031
- */
1032
-
1033
- /**
1034
- * Emitted when there is no more song in the queue and {@link Queue#autoplay} is `false`.
1035
- * DisTube will leave voice channel if {@link DisTubeOptions}.leaveOnFinish is `true`
1036
- *
1037
- * @event DisTube#finish
1038
- * @param {Discord.Message} message The message from guild channel
1039
- * @example
1040
- * distube.on("finish", message => message.channel.send("No more song in queue"));
1041
- */
1042
-
1043
- /**
1044
- * `@2.3.0` Emitted when DisTube initialize a queue to change queue default properties.
1045
- *
1046
- * @event DisTube#initQueue
1047
- * @param {Queue} queue The guild queue
1048
- * @example
1049
- * distube.on("initQueue", queue => {
1050
- * queue.autoplay = false;
1051
- * queue.volume = 100;
1052
- * });
1053
- */
1054
-
1055
- /**
1056
- * Emitted when {@link Queue#autoplay} is `true`, the {@link Queue#songs} is empty and
1057
- * DisTube cannot find related songs to play
1058
- *
1059
- * @event DisTube#noRelated
1060
- * @param {Discord.Message} message The message from guild channel
1061
- * @example
1062
- * distube.on("noRelated", message => message.channel.send("Can't find related video to play. Stop playing music."));
1063
- */
1064
-
1065
- /**
1066
- * Emitted after DisTube play the first song of the playlist
1067
- * and add the rest to the guild queue
1068
- *
1069
- * @event DisTube#playList
1070
- * @param {Discord.Message} message The message from guild channel
1071
- * @param {Queue} queue The guild queue
1072
- * @param {Playlist} playlist Playlist info
1073
- * @param {Song} song Playing song
1074
- * @example
1075
- * const status = (queue) => `Volume: \`${queue.volume}%\` | Loop: \`${queue.repeatMode ? queue.repeatMode == 2 ? "Server Queue" : "This Song" : "Off"}\` | Autoplay: \`${queue.autoplay ? "On" : "Off"}\``;
1076
- * distube.on("playList", (message, queue, playlist, song) => message.channel.send(
1077
- * `Play \`${playlist.name}\` playlist (${playlist.songs.length} songs).\nRequested by: ${song.user}\nNow playing \`${song.name}\` - \`${song.formattedDuration}\`\n${status(queue)}`
1078
- * ));
1079
- */
1080
-
1081
- /**
1082
- * Emitted when DisTube play a song.
1083
- * If {@link DisTubeOptions}.emitNewSongOnly is `true`, event is not emitted when looping a song or next song is the previous one
1084
- *
1085
- * @event DisTube#playSong
1086
- * @param {Discord.Message} message The message from guild channel
1087
- * @param {Queue} queue The guild queue
1088
- * @param {Song} song Playing song
1089
- * @example
1090
- * const status = (queue) => `Volume: \`${queue.volume}%\` | Loop: \`${queue.repeatMode ? queue.repeatMode == 2 ? "Server Queue" : "This Song" : "Off"}\` | Autoplay: \`${queue.autoplay ? "On" : "Off"}\``;
1091
- * distube.on("playSong", (message, queue, song) => message.channel.send(
1092
- * `Playing \`${song.name}\` - \`${song.formattedDuration}\`\nRequested by: ${song.user}\n${status(queue)}`
1093
- * ));
1094
- */
1095
-
1096
- /**
1097
- * Emitted when {@link DisTubeOptions}.searchSongs is `true`.
1098
- * Search will be canceled if user's next message is invalid number or timeout (60s)
1099
- *
1100
- * @event DisTube#searchCancel
1101
- * @param {Discord.Message} message The message from guild channel
1102
- * @example
1103
- * // DisTubeOptions.searchSongs = true
1104
- * distube.on("searchCancel", (message) => message.channel.send(`Searching canceled`));
1105
- */
1106
-
1107
- /**
1108
- * Emitted when {@link DisTubeOptions}.searchSongs is `true`.
1109
- * DisTube will wait for user's next message to choose song manually
1110
- * if song param of {@link DisTube#play|play()} is invalid url
1111
- *
1112
- * @event DisTube#searchResult
1113
- * @param {Discord.Message} message The message from guild channel
1114
- * @param {SearchResult[]} result Searched result (max length = 12)
1115
- * @example
1116
- * // DisTubeOptions.searchSongs = true
1117
- * distube.on("searchResult", (message, result) => {
1118
- * let i = 0;
1119
- * message.channel.send(`**Choose an option from below**\n${result.map(song => `**${++i}**. ${song.name} - \`${song.formattedDuration}\``).join("\n")}\n*Enter anything else or wait 60 seconds to cancel*`);
1120
- * });
1121
- */