如何在Discord.js开发的音乐机器人中添加播放队列与切歌功能
实现方案
第一步:定义全局服务器队列
首先在你的机器人入口文件(通常是index.js)最顶部定义全局Map,用来存储每个服务器独立的播放队列,挂载到client上方便所有命令文件访问:
// 全局队列存储,key为服务器ID,value为队列信息对象 const serverQueues = new Map(); client.serverQueues = serverQueues;
每个服务器的队列对象包含3个核心字段:
- voiceConnection:当前语音连接实例
- songs:待播放的歌曲数组,每个元素存储歌曲的url、标题等信息
- playing:当前是否正在播放的状态标记
第二步:修改播放命令逻辑
替换你原有的p.js播放命令代码,逻辑调整为:如果当前服务器没有正在播放的内容就直接播放请求的歌曲,已有内容在播放则把歌曲加入队列等待播放:
const ytdl = require('ytdl-core'); const ytSearch = require('yt-search'); module.exports = { name: 'p', description: '播放YouTube音乐,支持链接/关键词搜索', async execute(message, args) { const voiceChannel = message.member.voice.channel; if (!voiceChannel) return message.channel.send('你需要加入语音频道才能使用该命令!'); const permissions = voiceChannel.permissionsFor(message.client.user); if (!permissions.has('CONNECT')) return message.channel.send('我没有加入该语音频道的权限'); if (!permissions.has('SPEAK')) return message.channel.send('我没有在该语音频道说话的权限'); if (!args.length) return message.channel.send('请输入要播放的歌曲链接或关键词'); // 校验URL的工具函数 const validURL = (str) =>{ const regex = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/; return regex.test(str); } // 获取当前服务器的队列,不存在就初始化 const queue = message.client.serverQueues.get(message.guild.id) || { voiceConnection: null, songs: [], playing: false }; // 解析要播放的歌曲 let song; if(validURL(args[0])){ const songInfo = await ytdl.getInfo(args[0]); song = { title: songInfo.videoDetails.title, url: songInfo.videoDetails.video_url } }else{ const videoFinder = async (query) => { const videoResult = await ytSearch(query); return (videoResult.videos.length > 1) ? videoResult.videos[0] : null; } const video = await videoFinder(args.join(' ')); if(!video) return message.channel.send('没有找到对应的歌曲'); song = { title: video.title, url: video.url } } // 无播放内容时直接播放,否则加入队列 if(!queue.playing){ queue.songs.push(song); queue.playing = true; queue.voiceConnection = await voiceChannel.join(); message.client.serverQueues.set(message.guild.id, queue); playSong(message.guild, queue.songs[0]); await message.reply(`:thumbsup: 正在播放 ***${song.title}***`) }else{ queue.songs.push(song); message.client.serverQueues.set(message.guild.id, queue); await message.reply(`:musical_note: 已将 ***${song.title}*** 加入播放队列,当前队列位置 ${queue.songs.length}`) } // 封装播放函数,歌曲播放完成自动触发下一首 async function playSong(guild, song){ const serverQueue = message.client.serverQueues.get(guild.id); if(!song){ serverQueue.voiceConnection.leave(); message.client.serverQueues.delete(guild.id); return message.channel.send('队列所有歌曲播放完毕,已退出语音频道'); } const stream = ytdl(song.url, {filter: 'audioonly'}); serverQueue.voiceConnection.play(stream, {seek: 0, volume: 1}) .on('finish', () =>{ serverQueue.songs.shift(); playSong(guild, serverQueue.songs[0]); }); } } }
第三步:实现skip切歌命令
新建skip.js命令文件,代码如下:
module.exports = { name: 'skip', description: '切歌,跳过当前正在播放的歌曲', async execute(message) { const voiceChannel = message.member.voice.channel; if (!voiceChannel) return message.channel.send('你需要加入语音频道才能使用切歌命令!'); const serverQueue = message.client.serverQueues.get(message.guild.id); if(!serverQueue || !serverQueue.playing) return message.channel.send('当前没有正在播放的歌曲'); // 结束当前播放,自动触发finish事件播放下一首 serverQueue.voiceConnection.dispatcher.end(); await message.reply(`:track_next: 已跳过当前歌曲`); } }
内容的提问来源于stack exchange,提问作者AndiTheMajor
相关产品推荐
相关产品推荐

