こんにちは。フンです。
最近、NLP(Natural Language Processing)に興味を持ってるので、勉強しながらチャットボットのシリーズを作りたいと思います。
今回ですね、Discordというチャットアプリで、人のコマンドを理解し、音楽を再生できる見たいなチャットボットを作りましょう!
#とりあえずボットの概観に行きましょう!
① ボットのスキルを先に決める
友達とボットとどっちとチャットするか別けるため、!
を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 | 音楽リストを表示する |
② ボットのサーバー構造
- ボットのサーバー: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の所を注意してくださいね。後使うよう。
- 以下にみるとボットの権限を設定する所だ。全部VoicePermissionをチェックしてね。生成される権限の数(PERMISSION_ID)とCLIENT_IDをコーピしてね。
-
ボットをDiscordに接続するため、以下のリンクをアクセスしてください:
https://discordapp.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot&permissions=PERMISSION_ID
-
DiscordのSeverでボットを見えると①が成功だ!
② 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ファイルに
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",
}
- ボットがメッセンジを処理するソース
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);
}
});
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
まとめ
簡単なボットを開発完了になりました。
コマンドでボットをテストしてください。
音楽を聴きながら友達とゲームしてくださいね!
今度はコマンドだけではなく、自然言語をボットが理解し、音楽を再生できる機能を皆さんに紹介させていただきます。
見てくれてありがとうございました!。
Gitリンク:https://github.com/tqhung04/Utadorei