#javascript #audio #youtube-api #bots #discord
#javascript #Аудио #youtube-api #боты #Discord
Вопрос:
В данный момент я занимаюсь написанием бота для Discord.
Мне удалось заставить его запрашивать YouTube на основе заданных поисковых запросов, а затем воспроизводить аудио из верхнего результата, однако я столкнулся с проблемой, когда я пытаюсь поставить звуковые дорожки в очередь в списке и воспроизвести их после завершения аудиопотока.
const API_KEY = "<my API Key>";
var discord = require("discord.js");
var ytdl = require('ytdl-core');
var request = require('superagent');
var bot = new discord.Client();
var voiceChannel = null;
var ytAudioQueue = [];
bot.on('ready', function() {
console.log('I am ready');
});
bot.on('message', function(message) {
var messageParts = message.content.split(' ');
var command = messageParts[0].toLowerCase();
var parameters = messageParts.splice(1, messageParts.length);
console.log("command: " command);
console.log("parameters: " parameters);
switch (command) {
case "hi":
message.reply("Hey there!");
break;
case "*help":
HelpCommand(message);
break;
case "*join":
message.reply("Attempting to join channel: " parameters[0]);
JoinCommand(parameters[0], message);
break;
case "*play":
PlayCommand(parameters.join(" "), message);
break;
}
});
voiceChannel.on('speaking', (user, speaking) => {
// the audio has finished playing, so remove it from the queue and start playing the next song
if (!speaking amp;amp; ytAudioQueue.length > 1) {
ytAudioQueue.pop();
if (voiceChannel == null) {
JoinCommand(bot.channels.find(val => val.type === 'voice').name).then(function() {
PlayStream(ytAudioQueue.first);
});
}
else {
PlayStream(ytAudioQueue.first);
}
}
});
/* COMMAND HANDLERS */
/// lists out all of the bot commands
function HelpCommand(originalMessage) {
originalMessage.reply("*join <channel-to-join> - Connects to bot to a channel by channel name");
originalMessage.reply("*play <YouTube search term> - Plays audio from YouTube based on the search term");
}
/// plays audio based on results from youtube search
function PlayCommand(searchTerm) {
//bot.sendMessage("Searching Youtube for audio...");
YoutubeSearch(searchTerm);
}
/// joins the bot to the specified voice channel
function JoinCommand(channelName) {
if (voiceChannel) {
voiceChannel.disconnet();
}
var voiceChannel = GetChannelByName(channelName);
return voiceChannel.join();
}
/* END COMMAND HANDLERS */
/* HELPER METHODS */
/// returns the channel that matches the name provided
function GetChannelByName(name) {
var channel = bot.channels.find(val => val.name === name);
return channel;
}
function YoutubeSearch(searchKeywords) {
var requestUrl = 'https://www.googleapis.com/youtube/v3/search' `?part=snippetamp;q=${escape(searchKeywords)}amp;key=${API_KEY}`;
request(requestUrl, (error, response) => {
if (!error amp;amp; response.statusCode == 200) {
var body = response.body;
if (body.items.length == 0) {
console.log("Your search gave 0 results");
return videoId;
}
for (var item of body.items) {
if (item.id.kind === 'youtube#video') {
QueueYtAudioStream(item.id.videoId);
}
}
}
else {
console.log("Unexpected error when searching YouTube");
return null;
}
});
return null;
}
/// Queues result of Youtube search into stream
function QueueYtAudioStream(videoId) {
var streamUrl = `https://www.youtube.com/watch?v=${videoId}`;
ytAudioQueue.push(streamUrl);
}
// plays a given stream
function PlayStream(streamUrl) {
const streamOptions = {seek: 0, volume: 1};
console.log("Streaming audio from " streamUrl);
if (streamUrl) {
const stream = ytdl(streamUrl, {filter: 'audioonly'});
const dispatcher = bot.voiceConnections.first().playStream(stream, streamOptions);
}
}
/* END HELPER METHODS */
bot.login("<BOT LOGIN ID HERE>");
https://jsfiddle.net/o3Lvqt94/
Моя идея заключалась в том, что я мог бы начать воспроизведение следующего аудиопотока, как только голосовой канал перестанет говорить, однако событие завершается неудачей, поскольку это не установлено при запуске, хотя я мог бы это сделать, я не думаю, что это удовлетворит мои потребности, поскольку голосовой канал можно изменить другой командой.
Ответ №1:
Я использовал ваш код в качестве основы для своего собственного бота Discord и устранил ошибку, о которой вы упомянули. Я просто изменил
dispatcher.on('end', () => {
PlayNextStreamInQueue();
});
Для
dispatcher.on('end', () => {
dispatcher = null;
PlayNextStreamInQueue();
});
Комментарии:
1. рад слышать, что моему коду нашли хорошее применение 🙂 Я попробую это исправить!
Ответ №2:
Мое решение.
index.js
var discord = require("discord.js"); // discord library
var ytdl = require('ytdl-core'); // youtube download library
var youtube = require('./youtube.js'); // performs youtube API requests
var bot = new discord.Client();
var ytAudioQueue = [];
var dispatcher = null;
bot.on('ready', function () {
console.log('I am ready');
});
bot.on('message', function (message) {
var messageParts = message.content.split(' ');
var command = messageParts[0].toLowerCase();
var parameters = messageParts.splice(1, messageParts.length);
console.log("command: " command);
console.log("parameters: " parameters);
switch (command) {
case "hi":
message.reply("Hey there!");
break;
case "*help":
HelpCommand(message);
break;
case "*join":
message.reply("Attempting to join channel: " parameters[0]);
JoinCommand(parameters[0]);
break;
case "*play":
PlayCommand(parameters.join(" "), message);
break;
case "*playqueue":
PlayQueueCommand(message);
break;
}
});
/* COMMAND HANDLERS */
/// lists out all of the bot commands
function HelpCommand(originalMessage) {
originalMessage.reply("*join <channel-to-join> - Connects to bot to a channel by channel name");
originalMessage.reply("*play <YouTube search term> - Plays audio from YouTube based on the search term");
originalMessage.reply("*playqueue - Lists the audio remaining in the play queue");
}
/// plays audio based on results from youtube search
function PlayCommand(searchTerm) {
// if not connected to a voice channel then connect to first one
if (bot.voiceConnections.array().length == 0) {
var defaultVoiceChannel = bot.channels.find(val => val.type === 'voice').name;
JoinCommand(defaultVoiceChannel);
}
// search youtube using the given search search term and perform callback action if video is found
youtube.search(searchTerm, QueueYtAudioStream);
}
/// lists out all music queued to play
function PlayQueueCommand(message) {
var queueString = "";
for(var x = 0; x < ytAudioQueue.length; x ) {
queueString = ytAudioQueue[x].videoName ", ";
}
queueString = queueString.substring(0, queueString.length - 2);
message.reply(queueString);
}
/// joins the bot to the specified voice channel
function JoinCommand(channelName) {
var voiceChannel = GetChannelByName(channelName);
if (voiceChannel) {
voiceChannel.join();
console.log("Joined " voiceChannel.name);
}
return voiceChannel;
}
/* END COMMAND HANDLERS */
/*----------------------------------------------------------------------*/
/* HELPER METHODS */
/// returns the channel that matches the name provided
function GetChannelByName(name) {
var channel = bot.channels.find(val => val.name === name);
return channel;
}
/// Queues result of Youtube search into stream
function QueueYtAudioStream(videoId, videoName) {
var streamUrl = `${youtube.watchVideoUrl}${videoId}`;
if (!ytAudioQueue.length) {
ytAudioQueue.push(
{
'streamUrl': streamUrl,
'videoName': videoName
}
);
console.log("Queued audio " videoName);
PlayStream(ytAudioQueue[0].streamUrl);
}
else {
ytAudioQueue.push(
{
'streamUrl': streamUrl,
'videoName': videoName
}
);
console.log("Queued audio " videoName);
}
}
/// Plays a given stream
function PlayStream(streamUrl) {
const streamOptions = {seek: 0, volume: 1};
if (streamUrl) {
const stream = ytdl(streamUrl, {filter: 'audioonly'});
if (dispatcher == null) {
var voiceConnection = bot.voiceConnections.first();
//console.log(voiceConnection);
if (voiceConnection) {
console.log("Now Playing " ytAudioQueue[0].videoName);
dispatcher = bot.voiceConnections.first().playStream(stream, streamOptions);
dispatcher.on('end', () => {
PlayNextStreamInQueue();
});
dispatcher.on('error', (err) => {
console.log(err);
});
}
}
else {
dispatcher = bot.voiceConnections.first().playStream(stream, streamOptions);
}
}
}
/// Plays the next stream in the queue
function PlayNextStreamInQueue() {
ytAudioQueue.splice(0, 1);
// if there are streams remaining in the queue then try to play
if (ytAudioQueue.length != 0) {
console.log("Now Playing " ytAudioQueue[0].videoName);
PlayStream(ytAudioQueue[0].streamUrl);
}
}
/* END HELPER METHODS */
bot.login("redacted");
youtube.js
var request = require('superagent');
const API_KEY = "redacted";
const WATCH_VIDEO_URL = "https://www.youtube.com/watch?v=";
exports.watchVideoUrl = WATCH_VIDEO_URL;
exports.search = function search(searchKeywords, callback) {
var requestUrl = 'https://www.googleapis.com/youtube/v3/search' `?part=snippetamp;q=${escape(searchKeywords)}amp;key=${API_KEY}`;
request(requestUrl, (error, response) => {
if (!error amp;amp; response.statusCode == 200) {
var body = response.body;
if (body.items.length == 0) {
console.log("Your search gave 0 results");
return;
}
for (var item of body.items) {
if (item.id.kind === 'youtube#video') {
callback(item.id.videoId, item.snippet.title);
return; // prevent adding entire list of youtube videos
}
}
}
else {
console.log("Unexpected error when searching YouTube");
return;
}
});
return;
};
Комментарии:
1. в этом решении все еще есть некоторые ошибки, иногда звук в очереди воспроизводится, иногда нет. Я не уверен, что не так с логикой.
Ответ №3:
Я бы сказал, сохранить очередь в массиве
var que = {
0 = "LINK";
};
и вы восстанавливаете его с помощью
que[0]
и автоматическая очередь
function skip() {
for (var i = 0; var length = que.length; i < length; i ) {
if (i != length) {
que[i] = que[(i 1)];
} else {
que[i] = null;
}
}
}
чтобы показать очередь
function showque() {
var queText = "";
for (var i = 0; var length = que.length; i < length; i ) {
queText = queText "[" i "] " que[i] "n";
}
return queText;
}
фрагмент сообщения для отправки
message("ANYTHING BEFORE THE QUEn" showque() "ANYTHING AFTER");
надеюсь, это должно сработать.