streamify-audio 2.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.
- package/LICENSE +21 -0
- package/README.md +430 -0
- package/bin/streamify.js +84 -0
- package/docs/README.md +31 -0
- package/docs/automation.md +186 -0
- package/docs/configuration.md +169 -0
- package/docs/discord/events.md +206 -0
- package/docs/discord/manager.md +180 -0
- package/docs/discord/player.md +180 -0
- package/docs/discord/queue.md +197 -0
- package/docs/examples/advanced-bot.md +391 -0
- package/docs/examples/basic-bot.md +182 -0
- package/docs/examples/lavalink.md +156 -0
- package/docs/filters.md +309 -0
- package/docs/http/endpoints.md +199 -0
- package/docs/http/server.md +172 -0
- package/docs/quick-start.md +92 -0
- package/docs/sources.md +141 -0
- package/docs/sponsorblock.md +95 -0
- package/index.d.ts +350 -0
- package/index.js +252 -0
- package/package.json +57 -0
- package/silence.ogg +0 -0
- package/src/cache/index.js +61 -0
- package/src/config.js +133 -0
- package/src/discord/Manager.js +453 -0
- package/src/discord/Player.js +658 -0
- package/src/discord/Queue.js +129 -0
- package/src/discord/Stream.js +252 -0
- package/src/filters/ffmpeg.js +270 -0
- package/src/providers/soundcloud.js +166 -0
- package/src/providers/spotify.js +216 -0
- package/src/providers/youtube.js +320 -0
- package/src/server.js +318 -0
- package/src/utils/logger.js +139 -0
- package/src/utils/stream.js +108 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Advanced Discord Bot
|
|
2
|
+
|
|
3
|
+
A full-featured music bot with queue management, filters, and automation.
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
|
|
7
|
+
const Streamify = require('streamify-audio');
|
|
8
|
+
|
|
9
|
+
const client = new Client({
|
|
10
|
+
intents: [
|
|
11
|
+
GatewayIntentBits.Guilds,
|
|
12
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
13
|
+
GatewayIntentBits.GuildMessages,
|
|
14
|
+
GatewayIntentBits.MessageContent
|
|
15
|
+
]
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const manager = new Streamify.Manager(client, {
|
|
19
|
+
ytdlpPath: '/usr/local/bin/yt-dlp',
|
|
20
|
+
ffmpegPath: '/usr/bin/ffmpeg',
|
|
21
|
+
cookiesPath: './cookies.txt',
|
|
22
|
+
spotify: {
|
|
23
|
+
clientId: process.env.SPOTIFY_CLIENT_ID,
|
|
24
|
+
clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
25
|
+
},
|
|
26
|
+
defaultVolume: 80,
|
|
27
|
+
sponsorblock: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
categories: ['sponsor', 'selfpromo']
|
|
30
|
+
},
|
|
31
|
+
autoLeave: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
emptyDelay: 30000,
|
|
34
|
+
inactivityTimeout: 300000
|
|
35
|
+
},
|
|
36
|
+
autoPause: {
|
|
37
|
+
enabled: true,
|
|
38
|
+
minUsers: 1
|
|
39
|
+
},
|
|
40
|
+
autoplay: {
|
|
41
|
+
enabled: false,
|
|
42
|
+
maxTracks: 5
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
client.on('ready', () => {
|
|
47
|
+
console.log(`Logged in as ${client.user.tag}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
client.on('messageCreate', async (message) => {
|
|
51
|
+
if (message.author.bot) return;
|
|
52
|
+
if (!message.content.startsWith('!')) return;
|
|
53
|
+
|
|
54
|
+
const [command, ...args] = message.content.slice(1).split(' ');
|
|
55
|
+
const query = args.join(' ');
|
|
56
|
+
|
|
57
|
+
let player = manager.get(message.guild.id);
|
|
58
|
+
|
|
59
|
+
switch (command) {
|
|
60
|
+
case 'play':
|
|
61
|
+
case 'p': {
|
|
62
|
+
if (!query) return message.reply('Provide a search query or URL.');
|
|
63
|
+
|
|
64
|
+
const vc = message.member.voice.channel;
|
|
65
|
+
if (!vc) return message.reply('Join a voice channel first.');
|
|
66
|
+
|
|
67
|
+
await message.react('đ');
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Check if playlist
|
|
71
|
+
const isPlaylist = query.includes('playlist') || query.includes('/album/');
|
|
72
|
+
|
|
73
|
+
if (isPlaylist) {
|
|
74
|
+
const result = await manager.loadPlaylist(query);
|
|
75
|
+
if (result.loadType === 'error') {
|
|
76
|
+
await message.reactions.removeAll();
|
|
77
|
+
return message.reply(`Failed: ${result.error}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!player) {
|
|
81
|
+
player = await manager.create(message.guild.id, vc.id, message.channel.id);
|
|
82
|
+
setupEvents(player, message.channel);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const tracks = result.tracks;
|
|
86
|
+
const first = tracks.shift();
|
|
87
|
+
first.requestedBy = message.author;
|
|
88
|
+
|
|
89
|
+
if (tracks.length > 0) {
|
|
90
|
+
tracks.forEach(t => t.requestedBy = message.author);
|
|
91
|
+
player.queue.addMany(tracks);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await message.reactions.removeAll();
|
|
95
|
+
message.reply(`đ Loaded **${result.playlist.title}** (${tracks.length + 1} tracks)`);
|
|
96
|
+
await player.play(first);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Single track
|
|
101
|
+
const result = await manager.resolve(query);
|
|
102
|
+
if (!result.tracks.length) {
|
|
103
|
+
await message.reactions.removeAll();
|
|
104
|
+
return message.reply('No results found.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const track = result.tracks[0];
|
|
108
|
+
track.requestedBy = message.author;
|
|
109
|
+
|
|
110
|
+
if (!player) {
|
|
111
|
+
player = await manager.create(message.guild.id, vc.id, message.channel.id);
|
|
112
|
+
setupEvents(player, message.channel);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await message.reactions.removeAll();
|
|
116
|
+
await player.play(track);
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
await message.reactions.removeAll();
|
|
120
|
+
message.reply(`Error: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case 'add':
|
|
126
|
+
case 'a': {
|
|
127
|
+
if (!player) return message.reply('No active player.');
|
|
128
|
+
if (!query) return message.reply('Provide a search query.');
|
|
129
|
+
|
|
130
|
+
const result = await manager.search(query);
|
|
131
|
+
if (!result.tracks.length) return message.reply('No results.');
|
|
132
|
+
|
|
133
|
+
const track = result.tracks[0];
|
|
134
|
+
track.requestedBy = message.author;
|
|
135
|
+
player.queue.add(track);
|
|
136
|
+
|
|
137
|
+
message.reply(`Added **${track.title}** to queue (#${player.queue.size})`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case 'skip':
|
|
142
|
+
case 's': {
|
|
143
|
+
if (!player?.playing) return message.reply('Nothing playing.');
|
|
144
|
+
await player.skip();
|
|
145
|
+
message.react('âī¸');
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
case 'prev':
|
|
150
|
+
case 'previous': {
|
|
151
|
+
if (!player) return message.reply('No player.');
|
|
152
|
+
const prev = await player.previous();
|
|
153
|
+
if (prev) message.react('âŽī¸');
|
|
154
|
+
else message.reply('No previous track.');
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'stop': {
|
|
159
|
+
if (!player) return message.reply('No player.');
|
|
160
|
+
player.stop();
|
|
161
|
+
message.react('âšī¸');
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'pause': {
|
|
166
|
+
if (!player?.playing) return message.reply('Nothing playing.');
|
|
167
|
+
player.pause();
|
|
168
|
+
message.react('â¸ī¸');
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case 'resume': {
|
|
173
|
+
if (!player?.paused) return message.reply('Not paused.');
|
|
174
|
+
await player.resume();
|
|
175
|
+
message.react('âļī¸');
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case 'seek': {
|
|
180
|
+
if (!player?.playing) return message.reply('Nothing playing.');
|
|
181
|
+
const seconds = parseInt(query);
|
|
182
|
+
if (isNaN(seconds)) return message.reply('Provide seconds.');
|
|
183
|
+
await player.seek(seconds * 1000);
|
|
184
|
+
message.reply(`Seeked to ${seconds}s`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'queue':
|
|
189
|
+
case 'q': {
|
|
190
|
+
if (!player) return message.reply('No player.');
|
|
191
|
+
|
|
192
|
+
const { current, tracks } = player.queue;
|
|
193
|
+
let desc = current
|
|
194
|
+
? `**Now Playing:** ${current.title}\n\n`
|
|
195
|
+
: 'Nothing playing.\n\n';
|
|
196
|
+
|
|
197
|
+
if (tracks.length > 0) {
|
|
198
|
+
desc += '**Up Next:**\n';
|
|
199
|
+
desc += tracks.slice(0, 10).map((t, i) =>
|
|
200
|
+
`${i + 1}. ${t.title}${t.isAutoplay ? ' đģ' : ''}`
|
|
201
|
+
).join('\n');
|
|
202
|
+
if (tracks.length > 10) {
|
|
203
|
+
desc += `\n... and ${tracks.length - 10} more`;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
desc += 'Queue is empty.';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
message.reply({
|
|
210
|
+
embeds: [new EmbedBuilder()
|
|
211
|
+
.setTitle('Queue')
|
|
212
|
+
.setDescription(desc)
|
|
213
|
+
.setFooter({ text: `Loop: ${player.queue.repeatMode} | Autoplay: ${player.autoplay.enabled ? 'on' : 'off'}` })
|
|
214
|
+
]
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'shuffle': {
|
|
220
|
+
if (!player) return message.reply('No player.');
|
|
221
|
+
player.queue.shuffle();
|
|
222
|
+
message.react('đ');
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case 'loop': {
|
|
227
|
+
if (!player) return message.reply('No player.');
|
|
228
|
+
const modes = ['off', 'track', 'queue'];
|
|
229
|
+
const current = modes.indexOf(player.queue.repeatMode);
|
|
230
|
+
const next = modes[(current + 1) % 3];
|
|
231
|
+
player.setLoop(next);
|
|
232
|
+
message.reply(`Loop: **${next}**`);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'autoplay':
|
|
237
|
+
case 'ap': {
|
|
238
|
+
if (!player) return message.reply('No player.');
|
|
239
|
+
const enabled = player.setAutoplay(!player.autoplay.enabled);
|
|
240
|
+
message.reply(`Autoplay: **${enabled ? 'on' : 'off'}**`);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case 'vol':
|
|
245
|
+
case 'volume': {
|
|
246
|
+
if (!player) return message.reply('No player.');
|
|
247
|
+
const vol = parseInt(query);
|
|
248
|
+
if (isNaN(vol)) return message.reply(`Volume: ${player.volume}%`);
|
|
249
|
+
player.setVolume(vol);
|
|
250
|
+
message.react('đ');
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case 'bass': {
|
|
255
|
+
if (!player) return message.reply('No player.');
|
|
256
|
+
await player.setFilter('bass', parseInt(query) || 10);
|
|
257
|
+
message.reply(`Bass: ${parseInt(query) || 10}`);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'nightcore':
|
|
262
|
+
case 'nc': {
|
|
263
|
+
if (!player) return message.reply('No player.');
|
|
264
|
+
await player.setFilter('nightcore', true);
|
|
265
|
+
message.reply('Nightcore enabled');
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'vaporwave':
|
|
270
|
+
case 'vw': {
|
|
271
|
+
if (!player) return message.reply('No player.');
|
|
272
|
+
await player.setFilter('vaporwave', true);
|
|
273
|
+
message.reply('Vaporwave enabled');
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case '8d': {
|
|
278
|
+
if (!player) return message.reply('No player.');
|
|
279
|
+
await player.setFilter('8d', true);
|
|
280
|
+
message.reply('8D enabled');
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'karaoke': {
|
|
285
|
+
if (!player) return message.reply('No player.');
|
|
286
|
+
await player.setFilter('karaoke', true);
|
|
287
|
+
message.reply('Karaoke enabled');
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'clearfilters':
|
|
292
|
+
case 'cf': {
|
|
293
|
+
if (!player) return message.reply('No player.');
|
|
294
|
+
await player.clearFilters();
|
|
295
|
+
message.reply('Filters cleared');
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case 'np':
|
|
300
|
+
case 'nowplaying': {
|
|
301
|
+
if (!player?.queue.current) return message.reply('Nothing playing.');
|
|
302
|
+
const t = player.queue.current;
|
|
303
|
+
const pos = Math.floor(player.position / 1000);
|
|
304
|
+
const filters = Object.keys(player.filters).filter(k => player.filters[k] && k !== 'volume');
|
|
305
|
+
|
|
306
|
+
message.reply({
|
|
307
|
+
embeds: [new EmbedBuilder()
|
|
308
|
+
.setTitle(t.isAutoplay ? 'đģ Autoplay' : 'âļī¸ Now Playing')
|
|
309
|
+
.setDescription(`**${t.title}**\nby ${t.author}`)
|
|
310
|
+
.setThumbnail(t.thumbnail)
|
|
311
|
+
.addFields(
|
|
312
|
+
{ name: 'Position', value: `${pos}s / ${t.duration}s`, inline: true },
|
|
313
|
+
{ name: 'Volume', value: `${player.volume}%`, inline: true },
|
|
314
|
+
{ name: 'Filters', value: filters.length ? filters.join(', ') : 'none', inline: true }
|
|
315
|
+
)
|
|
316
|
+
]
|
|
317
|
+
});
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'leave':
|
|
322
|
+
case 'dc': {
|
|
323
|
+
if (!player) return message.reply('Not connected.');
|
|
324
|
+
player.destroy();
|
|
325
|
+
message.react('đ');
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
function setupEvents(player, channel) {
|
|
332
|
+
player.on('trackStart', (track) => {
|
|
333
|
+
channel.send({
|
|
334
|
+
embeds: [new EmbedBuilder()
|
|
335
|
+
.setColor(track.isAutoplay ? 0x9b59b6 : 0x00ff00)
|
|
336
|
+
.setTitle(track.isAutoplay ? 'đģ Autoplay' : 'âļī¸ Now Playing')
|
|
337
|
+
.setDescription(`**${track.title}**\nby ${track.author}`)
|
|
338
|
+
.setThumbnail(track.thumbnail)
|
|
339
|
+
.setFooter({ text: track.requestedBy ? `Requested by ${track.requestedBy.tag}` : '' })
|
|
340
|
+
]
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
player.on('trackError', (track, error) => {
|
|
345
|
+
channel.send(`â Error: ${error.message}`);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
player.on('queueEnd', () => {
|
|
349
|
+
channel.send('đ Queue ended.');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
player.on('channelEmpty', () => {
|
|
353
|
+
channel.send('â ī¸ Channel empty, leaving in 30s...');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
player.on('autoPause', () => {
|
|
357
|
+
channel.send('â¸ī¸ Auto-paused (empty channel)');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
player.on('autoResume', () => {
|
|
361
|
+
channel.send('âļī¸ Resumed');
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Commands
|
|
369
|
+
|
|
370
|
+
| Command | Description |
|
|
371
|
+
|---------|-------------|
|
|
372
|
+
| `!play <query>` | Play song or playlist |
|
|
373
|
+
| `!add <query>` | Add to queue |
|
|
374
|
+
| `!skip` | Skip |
|
|
375
|
+
| `!prev` | Previous track |
|
|
376
|
+
| `!stop` | Stop |
|
|
377
|
+
| `!pause` / `!resume` | Pause/Resume |
|
|
378
|
+
| `!seek <seconds>` | Seek |
|
|
379
|
+
| `!queue` | View queue |
|
|
380
|
+
| `!shuffle` | Shuffle |
|
|
381
|
+
| `!loop` | Toggle loop |
|
|
382
|
+
| `!autoplay` | Toggle autoplay |
|
|
383
|
+
| `!vol <0-200>` | Volume |
|
|
384
|
+
| `!bass <-20 to 20>` | Bass filter |
|
|
385
|
+
| `!nightcore` | Nightcore |
|
|
386
|
+
| `!vaporwave` | Vaporwave |
|
|
387
|
+
| `!8d` | 8D audio |
|
|
388
|
+
| `!karaoke` | Karaoke |
|
|
389
|
+
| `!clearfilters` | Clear filters |
|
|
390
|
+
| `!np` | Now playing |
|
|
391
|
+
| `!leave` | Disconnect |
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Basic Discord Bot
|
|
2
|
+
|
|
3
|
+
A simple music bot with play, skip, and stop commands.
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
const { Client, GatewayIntentBits } = require('discord.js');
|
|
7
|
+
const Streamify = require('streamify-audio');
|
|
8
|
+
|
|
9
|
+
const client = new Client({
|
|
10
|
+
intents: [
|
|
11
|
+
GatewayIntentBits.Guilds,
|
|
12
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
13
|
+
GatewayIntentBits.GuildMessages,
|
|
14
|
+
GatewayIntentBits.MessageContent
|
|
15
|
+
]
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const manager = new Streamify.Manager(client, {
|
|
19
|
+
ytdlpPath: '/usr/local/bin/yt-dlp',
|
|
20
|
+
ffmpegPath: '/usr/bin/ffmpeg',
|
|
21
|
+
cookiesPath: './cookies.txt',
|
|
22
|
+
spotify: {
|
|
23
|
+
clientId: process.env.SPOTIFY_CLIENT_ID,
|
|
24
|
+
clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
25
|
+
},
|
|
26
|
+
defaultVolume: 80
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
client.on('ready', () => {
|
|
30
|
+
console.log(`Logged in as ${client.user.tag}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
client.on('messageCreate', async (message) => {
|
|
34
|
+
if (message.author.bot) return;
|
|
35
|
+
if (!message.content.startsWith('!')) return;
|
|
36
|
+
|
|
37
|
+
const [command, ...args] = message.content.slice(1).split(' ');
|
|
38
|
+
const query = args.join(' ');
|
|
39
|
+
|
|
40
|
+
const player = manager.get(message.guild.id);
|
|
41
|
+
|
|
42
|
+
switch (command) {
|
|
43
|
+
case 'play':
|
|
44
|
+
case 'p': {
|
|
45
|
+
if (!query) {
|
|
46
|
+
return message.reply('Please provide a search query or URL.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const voiceChannel = message.member.voice.channel;
|
|
50
|
+
if (!voiceChannel) {
|
|
51
|
+
return message.reply('You need to be in a voice channel.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = await manager.resolve(query);
|
|
56
|
+
|
|
57
|
+
if (!result.tracks.length) {
|
|
58
|
+
return message.reply('No results found.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const track = result.tracks[0];
|
|
62
|
+
track.requestedBy = message.author;
|
|
63
|
+
|
|
64
|
+
let p = player;
|
|
65
|
+
if (!p) {
|
|
66
|
+
p = await manager.create(
|
|
67
|
+
message.guild.id,
|
|
68
|
+
voiceChannel.id,
|
|
69
|
+
message.channel.id
|
|
70
|
+
);
|
|
71
|
+
setupEvents(p, message.channel);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await p.play(track);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(error);
|
|
77
|
+
message.reply(`Error: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'skip':
|
|
83
|
+
case 's': {
|
|
84
|
+
if (!player?.playing) {
|
|
85
|
+
return message.reply('Nothing is playing.');
|
|
86
|
+
}
|
|
87
|
+
await player.skip();
|
|
88
|
+
message.react('âī¸');
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'stop': {
|
|
93
|
+
if (!player) {
|
|
94
|
+
return message.reply('No active player.');
|
|
95
|
+
}
|
|
96
|
+
player.stop();
|
|
97
|
+
message.react('âšī¸');
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case 'pause': {
|
|
102
|
+
if (!player?.playing) {
|
|
103
|
+
return message.reply('Nothing is playing.');
|
|
104
|
+
}
|
|
105
|
+
player.pause();
|
|
106
|
+
message.react('â¸ī¸');
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case 'resume': {
|
|
111
|
+
if (!player?.paused) {
|
|
112
|
+
return message.reply('Not paused.');
|
|
113
|
+
}
|
|
114
|
+
await player.resume();
|
|
115
|
+
message.react('âļī¸');
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'leave':
|
|
120
|
+
case 'disconnect':
|
|
121
|
+
case 'dc': {
|
|
122
|
+
if (!player) {
|
|
123
|
+
return message.reply('Not in a voice channel.');
|
|
124
|
+
}
|
|
125
|
+
player.destroy();
|
|
126
|
+
message.react('đ');
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 'np':
|
|
131
|
+
case 'nowplaying': {
|
|
132
|
+
if (!player?.queue.current) {
|
|
133
|
+
return message.reply('Nothing is playing.');
|
|
134
|
+
}
|
|
135
|
+
const track = player.queue.current;
|
|
136
|
+
const position = Math.floor(player.position / 1000);
|
|
137
|
+
message.reply(`Now playing: **${track.title}** [${position}s / ${track.duration}s]`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function setupEvents(player, channel) {
|
|
144
|
+
player.on('trackStart', (track) => {
|
|
145
|
+
channel.send(`đĩ Now playing: **${track.title}**`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
player.on('trackError', (track, error) => {
|
|
149
|
+
channel.send(`â Error playing **${track.title}**: ${error.message}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
player.on('queueEnd', () => {
|
|
153
|
+
channel.send('đ Queue finished.');
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Running
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Set environment variables
|
|
164
|
+
export DISCORD_TOKEN=your_token
|
|
165
|
+
export SPOTIFY_CLIENT_ID=your_id
|
|
166
|
+
export SPOTIFY_CLIENT_SECRET=your_secret
|
|
167
|
+
|
|
168
|
+
# Run
|
|
169
|
+
node bot.js
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Commands
|
|
173
|
+
|
|
174
|
+
| Command | Description |
|
|
175
|
+
|---------|-------------|
|
|
176
|
+
| `!play <query>` | Play a song |
|
|
177
|
+
| `!skip` | Skip current song |
|
|
178
|
+
| `!stop` | Stop and clear queue |
|
|
179
|
+
| `!pause` | Pause playback |
|
|
180
|
+
| `!resume` | Resume playback |
|
|
181
|
+
| `!leave` | Disconnect |
|
|
182
|
+
| `!np` | Now playing |
|