15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Discordアプリで音楽を再生できるチャットボットを作る|Chatbot#1

Last updated at Posted at 2019-08-04

こんにちは。フンです。
最近、NLP(Natural Language Processing)に興味を持ってるので、勉強しながらチャットボットのシリーズを作りたいと思います。
今回ですね、Discordというチャットアプリで、人のコマンドを理解し、音楽を再生できる見たいなチャットボットを作りましょう!
ボット名は「Utadorei」に付ける^^

#とりあえずボットの概観に行きましょう!

① ボットのスキルを先に決める

友達とボットとどっちとチャットするか別けるため、!をprefixとしてチャットする

コマンド 説明
play 音楽を再生する 例:!play https://www.youtube.com/watch?v=SX_ViT4Ra7k
skip 次の歌を再生する 例: !skip
stop 音楽をストップする 例: !stop
playnow 音楽を最初に置いて、再生する 例:!playnow https://www.youtube.com/watch?v=SX_ViT4Ra7k
join チャットボットをVoiceチャネルを呼ぶ
np 現在再生されている歌の情報を表示する
list 音楽リストを表示する

② ボットのサーバー構造

Utadore#1 (1).png

  • ボットのサーバー:NodeJs、Express Framework、ytdl-coreのpackageを使う。ytdl-coreはYoutubeからビデオをダウンロードしてデータを変換してDiscordサーバにWebsocketの通りストリーミングします。
  • DiscordJSのAPI:https://discordjs.guide/に参考してください。
  • Step-by-stepで説明するので、心配しないでくださいねw。

具体的なステップに始めます

① Discordでボットを登録する

  • https://discordapp.com/developers/applications/にアクセスして、NewApplicationを登録する
  • 登録してから、左のNavigationで「Bot」をクリックして、Tokenの所を注意してくださいね。後使うよう。
discord2.png
  • 以下にみるとボットの権限を設定する所だ。全部VoicePermissionをチェックしてね。生成される権限の数(PERMISSION_ID)とCLIENT_IDをコーピしてね。
discord3.png
  • ボットをDiscordに接続するため、以下のリンクをアクセスしてください:
    https://discordapp.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot&permissions=PERMISSION_ID

  • DiscordのSeverでボットを見えると①が成功だ!

discord4.png

② Nodejsのパッケージをインストールします

以下のパッケージをインストールしてください。

discord.js - npm i discord.js
FFmpeg - npm i ffmpeg-binaries
node-opus - npm i node-opus
ytdl-core - npm i ytdl-core

③ ソースを書きましょう!

  • とりあえず、Nodeのサーバーを作ります。アプリのメインJsファイルに
server.js
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
require('./discord/index');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.get('/', function (req, res) {
  res.send('Welcome to my word! I am Utadorei :)');
});

app.listen(process.env.PORT || 3000, function() {
  console.log('Utadorei app listening on port 3000!');
});

  • 見やすくするようにdiscordのAPIを呼ぶのは別のファイルに書くます
require('./discord/index');
  • ボットのTokenとprefixをconfig.jsに書くます。
{
  "prefix": "!",
  "token": "xxx",
}

  • ボットがメッセンジを処理するソース
discord/index.js
const Discord = require('discord.js');
const { prefix, token } = require('./config.json');
const bot = new Discord.Client();
const HandleOrder = require('./services/handle_order.js');

bot.once('ready', () => {
  console.log('Ready!');
});

// メッセンジを聞く前にログインしましょう
bot.login(token);

// メッセンジが届いたら、このメソードを呼びます。
bot.on('message', message => {
  if (message.author.bot) { return; }
  
  // メッセンジの最初の言葉が!ならボットが実行する
  if (message.content.startsWith(prefix)) {
    HandleOrder.call(message);
  }
});
discord/services/handle_order.js
const { prefix } = require('../config.json');
const ytdl = require('ytdl-core');

module.exports = {
  call: call,
  play: play,
  isValidCommand: isValidCommand,
  handlePlay: handlePlay,
  handleSkip: handleSkip,
  handleStop: handleStop,
  handlePlayNow: handlePlayNow
}

let servers = {};

function call(message) {
  const args = message.content.slice(prefix.length).split(' ');
  const command = args.shift().toLowerCase();

  switch (command) {
    case 'play':
      handlePlay(message, args[0]);
      break;
    case 'skip':
      handleSkip(message);
      break;
    case 'stop':
      handleStop(message);
      break;
    case 'playnow':
      handlePlayNow(message, args[0]);
      break;
    case 'join':
      handleJoin(message);
      break;
    case 'np':
      handleNp(message);
      break;
    case 'list':
      handleList(message);
      break;
  }
}

function handleJoin(message) {
  if (!message.guild.voiceConnection) {
    message.member.voiceChannel.join()
      .then(connection => {
      })
      .catch(console.log);
  }
}

function play(connection, message) {
  let server = servers[message.guild.id];
  let unplay_queue = getFirstUnPlayedQueue(server.queue);
  server.dispatcher = connection.playStream(
    ytdl(unplay_queue.url)
  );
  console.log('Playing ' + unplay_queue.url);
  unplay_queue.status = 1;
  server.dispatcher.on('end', function() {
    let unplay_queue = getFirstUnPlayedQueue(server.queue);
    if(unplay_queue) { play(connection, message); }
    else { connection.disconnect(); }
  });
}

function isValidCommand(message, url) {
  if (!url) {
    message.reply('Please type a link.');
    return false;
  }

  let reg = /^(http(s)?:\/\/)?((w){3}.)?youtu(be|.be)?(\.com)?\/.+/gm;
  if (!url.match(reg)) {
    message.reply('Is that a valid youtube link?');
    return false;
  }

  if (!message.member.voiceChannel) {
    message.reply('Please go to the voice channel first.');
    return false;
  }

  return true;
}

function handlePlay(message, url) {
  if (!isValidCommand(message, url)) { return; }

  if (!servers[message.guild.id]) { servers[message.guild.id] = {queue: []}; }
  servers[message.guild.id].queue.push({
    url: url,
    status: 0
  });

  console.log(servers[message.guild.id].queue);

  if (!message.guild.voiceConnection) {
    message.member.voiceChannel.join()
      .then(connection => {
        play(connection, message);
      })
      .catch(console.log);
  }
}

function handleSkip(message) {
  if (!servers[message.guild.id]) { return; }

  let server = servers[message.guild.id];
  if (server.dispatcher) { server.dispatcher.end(); }
}

function handleStop(message) {
  if (message.guild.voiceConnection) { message.guild.voiceConnection.disconnect(); }
}

function handlePlayNow(message, url) {
  let server = servers[message.guild.id];
  if(!server) {
    handlePlay(message, url);
    return;
  }

  server.queue.splice(1, 0, {
    url: url,
    status: 0
  });
  server.dispatcher.end();
}

function handleNp(message) {
  let server = servers[message.guild.id];
  if(!server || !server.queue) {
    message.reply("There is not things.");
    return;
  }
  let current_queue = getCurrentQueue(server.queue);
  if(!server) { return; }
  ytdl.getBasicInfo(current_queue.url).then(info => {
    message.reply(getInfoMsg(info, current_queue.url));
  });
}

function getCurrentQueue(queues) {
  return queues.filter(function(queue) {
    return queue.status === 1;
  }).slice(-1)[0];
}

function getFirstUnPlayedQueue(queues) {
  return queues.filter(function(queue) {
    return queue.status === 0;
  })[0];
}

async function handleList(message) {
  let server = servers[message.guild.id];
  if(!server || !server.queue) {
    message.reply("There is not things.");
    return;
  }
  let msg = "";
  server.queue.forEach(function(queue) {
    ytdl.getBasicInfo(queue.url).then(info => {
      msg += getInfoMsg(info, queue.url) + '\n --------------------------';
      console.log(msg);
    });
  });

  await delay();
  message.reply(msg);
}

function getInfoMsg(info, url) {
  return `\n Title: ${info.player_response.videoDetails.title} \n Author: ${info.author.name} \n Link: ${url}`;
}

function delay() {
  return new Promise(resolve => setTimeout(resolve, 1000));
}
  • 今はサーバーをオープンしましょう!
npm start
Screen Shot 2019-08-05 at 0.41.24.png

まとめ

簡単なボットを開発完了になりました。
コマンドでボットをテストしてください。
音楽を聴きながら友達とゲームしてくださいね!

今度はコマンドだけではなく、自然言語をボットが理解し、音楽を再生できる機能を皆さんに紹介させていただきます。

見てくれてありがとうございました!。
Gitリンク:https://github.com/tqhung04/Utadorei

15
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?