【夏休み子ども?科学?電話?相談】DiscordのBotを作るには、特別なプログラミングが必要なの?

f:id:Szkieletor:20210809175211j:plain

話し手:鈴木・letor・太郎
東京仮想大学理学部パソコンカタカタ学科在学中。通常の学生は最大8年までしか在学できない東大で、休学により8年半という最長在学記録を打ち立てる(著者調べ)。病気により現在も記録を更新中。
Discord上でのBotの開発経験はなく、そもそも1年2か月コードを書いていないものの、質問に回答してあげる勇気を持つ聖人君子。
コミュニティではSzkieletorというハンドルネームを用いる。
ちなみに写真は別人。



f:id:Szkieletor:20210809181350j:plain

聞き手:Yさん
JavaScriptでプログラミングを始めて3日目。DiscordのBotを作ろうとして、サークルの質問チャンネルで相談した。
ちなみに写真は別人。



鈴木・letor・太郎(以下、Szki)
じゃあ、話します。ただ、私はDiscordのBotをちゃんと作ったことはないです。なので、間違っているところもあると思います。
いま、YさんはJavaScriptでどんなプログラムが書けますか?
コードを貼ってもらえると楽です。

Yさん(以下、Y) 
条件分岐までかなあという感じです。 スマホで入ってるのでコードは今ないですが、ひとまず10000以下の素数を列挙させることはできました。

Szki 
わかりました。では、できる範囲で解説してみます。

Y
ぜひ! お願いします。

Szki
まず、Yさんの質問から見てみましょう。

《Yさんの質問》

先日discordにbotを導入し、中身のコードはネットからコピペしたら動作させることはできました。

そこで、コードを自力で書いて、メッセージを送らせるとかリアクションをつけさせるといったことをしたいのですが、コードの具体的な書き方がわからないです。

そもそもそういったことをさせるためのDiscord botのための特別な文があるんでしょうか。それともプログラミングの初歩的な本に書いてある類の構文だけでbotを動かさねばならぬのでしょうか。

特別な文があると思ってぐぐったんですが見当たらなくて行き詰まりました。

うおん。

Szki 
なるほど。プログラミングを始めてみたけれども、実際にどうやってBotとして動くのか? という所にカベがありそうですね。

Szki
では、最初に状況を確認するために質問させてください。
その1。Yさんが使っている(プログラミング)言語は何ですか?
その2。「API」という言葉を知っていますか?

Y
言語はJavaScriptですが、私はプログラミング歴3日なので……:uon: :uon: :uon:
API分からず……以前ぐぐってこの言葉が出てきてなんだこれになりました。

Szki
わかりました。
すると、こちらの記事が参考になりそうですね。JavaScriptでDiscord Botを作ろうという入門記事です。

qiita.com

Szki
このサンプルコードを使って解説していきます。

const Discord = require('discord.js')
const client = new Discord.Client()
client.on('ready', () => {
  console.log(`${client.user.tag} でログインしています。`)
})

client.on('message', async msg => {
  if (msg.content === '!ping') {
    msg.channel.send('Pong!')
  }
})

client.login('トークン')

Y
はい、よろしくお願いします!

他のファイルのプログラムを使う

f:id:Szkieletor:20210809181233j:plain

const Discord = require('discord.js')

Szki
YさんはDiscordに「Botにメッセージをpostさせる」「チャンネルに投稿されたメッセージを取得する」みたいな色々なことをしたいわけですが、それは全部Yさんが書かなければいけないわけではありません。

Discordくんがやり方を用意してくれています。 「関数」というものに覚えさせておくと、それを簡単に実行することができます。 たとえば、msg.channel.send(送りたい内容の文字列)で、文字列を送れます。 こういう、「これがあればBotを開発できますセット」みたいなものをDiscordが用意してくれていて、それがdiscord.jsというJSファイルの中にあります。

const Discord = require('discord.js')

これは、プログラムの先頭で「俺は Discord という名前でdiscord.jsの中のものを使うぜ!」と宣言している文です。そうすると、次の行からdiscord.js内で用意されている便利な関数が使えるようになるんですね。

いま、厳密さは犠牲にして、とにかくわかりやすさ重視で解説しています。 Yさん、ここまで大丈夫ですか?

Y
少し大丈夫じゃないです…。
const Discord =Discord という名前の変数(定数?)を宣言して、それが require('discord.js') である、ってことで、だとしたら require って何やねんってなってました。

Szki
正直この部分はBotを書く上では全て共通なので、いまのYさんのレベル的に全てのキーワードを理解する必要はないと思います(私の意見です)
とりあえず一歩目として、「DiscordでBotを書くときは、discord.jsというDiscordくんが用意してくれたセットから関数を呼び出して使うのだなあ」さえわかればいいと思います。
ちなみに、これはわかっていましたか?

Y
今、まあそういうものなのかなってなりました。

Szki
正確には全て自分で書くこともできますが、それはとても大変だし、もうDiscordさんが用意してくれたものがあるので、みんなそれを使っています。

Y
なるほど。

Szki
DiscordやSlackなど、ユーザーが「何か拡張したい、アプリケーションやサーバーと通信して自分のプログラムに何かやらせたい」という欲求はよくユーザーに発生します。

でも、ユーザーが勝手になんでもできちゃうと、当然困るので(たとえば1秒間に1億回postするプログラムを実行されると、困る)

Discordくんなどが「こういう手順で通信してくれや!」みたいなのを用意してくれています

これがAPIです。

まあ、つまりはDiscordくんとしては「こっちでこうしたい場合これを使ってっていう関数は用意しとくから、それを使っていいよ」としておきます。 すごく雑な説明をしています まあAPIは「とりあえずDiscordでもSlackでも向こうが用意してくれる道/手順があって、それがAPIと呼ばれている」くらいの理解でいいと思います。 Yさんも含め、Botを動かしたい人はみんなAPIを利用してDiscordでBotを動かします。

const Discord = require('discord.js')
const client = new Discord.Client()

// やらせたいこと

client.login('トークン')

ここは、何やってるか分からないと思いますが、今のレベルなら「とりあえずAPIを使うために必要なおまじないの文字列」という認識でいいと思います。コピペしましょう。 このコメントの部分がやらせたいことを書くところで、いま貼った部分は通信のための手順や本質のための準備です。 @Yさん ここまで大丈夫ですか?

Y
見たら過去のコピペのおかげですでに出来てたのですが、最後がclient.login();になってました。これでもいいんでしょうか?

Szki
ふむ。 では先に、トークンというものについて説明しましょうか。

f:id:Szkieletor:20210809181754j:plain

ここはまあ後で追加で(上級者)さんとかに具体的にコードを見てもらうと良いと思います。 とりあえず、「自分のプログラムを動かしたい!」となったとき、SlackでもDiscordでも、「じゃあ何でもできる権限をあげるね☆」とはなりません。 「欲しい機能を選択して、そしたらその機能の使用許可をあげるね」という感じになります。 Tokenというのは、Botに一意に振られるマイナンバーのようなものです。

YさんがBotを作るぞ! となったら、まずこのマイナンバーの発行を申請します。 そのとき、Discordの場合は「じゃあこのBotの権限を設定してね」ということを要求されます。 すると、DiscordくんはTokenをデータベース的なものに登録するわけですね。

Szki
それで、Yさんがプログラムを書いたとき、「Discordさんと通信したいんですが、ぼくのトークンはこれです!」ということをプログラムに書いておきます。

つまり、Discordくんは相手が誰で何を許可したかわかるわけですね。

Szki

https://qiita.com/yuto0214w/items/1ecee25efca6b5b7445b

この記事もそうですが、Discord Bot作ろう系の入門記事は、必ず最初に「トークンを取得しようね」って書いてありますね。確認してみるといいと思います。

ちなみに、トークンが漏れると、他の人がYさんと偽って通信できるようになるので、破滅します。

この記事にもちゃんと書いてありますね。

client.login('トークンの文字列')

これは、トークンをプログラム中に示すひとつのやり方です ちょっとここは(上級者)さんに聞けば分かると思いますが、たぶんトークンの示し方が一通りではなく、ここで書かなくても良いということだと思います Yさんがコピペしたプログラムがどんなものかはわかりませんが、どこかでtokenを示しているはずです まあこれも、Yさんがコピペしたやつで動いてるなら書き換えなくてもいいです 本質(Yさんがやらせたいことに関わる)ではないので。

client.on('ready', () => {
  console.log(`${client.user.tag} でログインしています。`)
})

client.on('message', async msg => {
  if (msg.content === '!ping') {
    msg.channel.send('Pong!')
  }
})

サンプルの残りはこれです。日本語で書き直してみます。

Discordから通信の準備が整った(readyになった)ら
  ターミナルに誰でログインしているか表示する
(おわり)

Discordからメッセージを受け取る
  受け取ったメッセージが"!ping"だったら
    "Pong!"を返す
(おわり)

まず、

client.on('ready', () => {
  *readyになったときにやらせたいことを書く*
})

について説明します。

readyのときにコンソールに表示する意義

Szki
Yさんはまだ分からないと思いますが、Botに限らず、自分のプログラムが通信可能になったかどうかはとても知りたくなる情報です。なので書いているのだと思います。 別に把握しなくても大丈夫、というのであれば、消しても問題ありません。

ただ、こう書いておくと、プログラムがバグった時に、そこそこ重要な役目を果たします。 「おや、!pingと送っても何も帰ってこないぞ」というとき、

  • 手順やトークンをミスっていて、そもそもプログラムが通信可能ではない
  • 通信可能だが、!pingと受け取ったりPongを返したりするところにミスがある

の2通りのミスの可能性がありえます。起動時に何も表示が出なければどちらの可能性もあるわけですが、この例のように書いておけば、「ターミナルにメッセージが出たから、通信はできたんだな」「メッセージが出ないから、通信ができていないんだな」、と、エラーの原因がどちらなのかを識別できますね。

プログラムが動かないとき、当然ですがまず原因を特定する必要があります。 そのとき、原因を特定するために細かく場合分けしてターミナルに何か表示させる、という手順は一番オーソドックスです。

プログラムを書くのであればいつかバグに苦しみます。 苦しんだ時に思い出してみてください。

他に、単にreadyになった瞬間にという条件で何かやらせたいという時にも使います。

client.on()とは何か

次に移ります。

client.on() は、

const Discord = require('discord.js')
const client = new Discord.Client()

が関わってくるんですが、つまり「Discord.jsの中のClient()というグループ内のon()という関数を使う」ということを言っています。 変数の格納とか難しいことはいいので、とりあえずイコールで置換して考えてみてください。 client.on()は、client = Discord.Client()なので、Discord.Client().onです。 //JSマンからツッコミが入るかもしれん さらに、Discord = discord.js なので、 Discord.Client().on()は、discord.jsのClient.on()、ということになります。

変数に格納する意味

Y
この小さいプログラムでわざわざこうやって短縮する意味はあるんでしょうか。

Szki
このサイズで完成ということなら、特に恩恵はないと思います。ただ、これから色々な処理を追加したい、という場合には、この書き方が効いてきます。 Discord.Client()はよく使うものなんでしょうね。 「何回も繰り返し同じ文字列を書く羽目になっていて、無駄だなあ」ってなったとき、変数を使うと便利です。

ただ、最初は小さいプログラムから書き始めて、だんだん大きくしていくのが慣れている人でも基本だと思います

慣れている人がコードを書くときも、まずこの例のような小さいプログラムから始めて、だんだん大きくしていく、という手順はオーソドックスです。すると、「後々何度も使いそうだから、最初から変数に入れて短くしておこう」と、プログラムが小さいうちから変数に入れる、ということが起こります。

全くこういうことを意識しないと、プログラムが大きくなったときに、これはやっぱり変数に入れたいな、と思っても、あちこちに影響が出てしまいます。そうすると直す作業がけっこう大変だったりします。 だから、このコードも含めて最初から拡張性を意識して書く、ということがなされています。でも、Yさんは初心者なのでまだ気にしなくても良いのではないでしょうか。

Y
完全に理解しました。

f:id:Szkieletor:20210809182122j:plain

client.on()のやっていること

Szki
最後はこの部分ですね。

client.on('message', async msg => {
  (受け取ったメッセージに対してやらせたいことを書く)
})

Discordの用意したライブラリのClient()on()くんは、一つ目のパラメータで、Discordくんから何を受け取った時に発動するかを指定しているようです。これは私の推測なので、ちゃんと知りたければドキュメントを読んでください。

ステータス

Szki
readymessage以外にも、ステータスは色々なものが存在します。たとえば接続が終了したことを示すもの、finishみたいな単語とか、あってもおかしくないですね。

このステータスとif文を組み合わせれば、どういうタイミングでプログラムが発動するかをYさんが操作できます。

'message'を指定すると全てのメッセージに対して発動するので、実はメッセージが"!ping"じゃなくてもこの関数の発動はしています。ただ次のif文で条件に合致しないので何もせずに終わっている、となります。

変数名

Szki
2つ目のパラメータ(「引数」と呼ばれます)にメッセージが入るというルールになっているようですね。これを使ってまた条件分岐させることができます。

ここは msg という名前を使わなくても大丈夫です。これはただの慣習的に使われている変数名なので、

client.on('message', async aaaa => {
  if (aaaa.content === '!ping') {
    aaaa.channel.send('Pong!')
  }
})

みたいに、別の名前に変えても問題なく動くと思います。違ったらすみません。 けれど、 aaaa という変数名だと、後で自分が読んだ際や他の人が読む際に、この変数の中身は何なのか、ぱっとわかりませんよね? コードを追う必要が出てきますね。 でも、 msg としておけば、これはメッセージが入ってるんだな、とみんなわかるので、そう命名してあります。


msg の中身

Szki
とりあえず、on()の第二引数の msg から、

  • msg.contentでメッセージの中身が取り出せます。
  • msg.channel.send('送りたい文字列')でメッセージを受け取ったチャンネルに送り返すことができます。

ということです。

Discordくんは、メッセージが来たことを知らせるときに、メッセージに関する色々な情報をひとまとめにして送ってきます。

msg = {
  content = 'おはよう'
  channel = #Uxxxxxxxxx //(英数字のIDで送ってきます)
  channel.send() = ... //(送り返す関数へのリンク)
  //(実は他にもたくさんある)
}

メッセージ情報には、たとえば

  • どのユーザーが書き込んだか
  • チャンネルはどこか
  • 日時はいつか

みたいなものがありますね。ユーザー情報ならchannel.userのような名前でアクセスできるはずです。それでif文を使えば、「ユーザーが○○さんだったら」のように分岐させられます。



Szki
……さて、これで説明したいことは終わったのですが、何かわからないところはありますか?

Y
大丈夫です。ありがとうございました!
あ、ぼく実は大富豪なので、お礼に5億円振り込んでおきますね!

Szki
やったー!



2021/08/09 18:35 配信



























注意

この記事は、インタビュー風の文章を書こうかなという動機のみで作成されたものです。
この記事内の技術的内容については、一切の正確性を保証しません。
内容に間違いがある場合、Szkieletor (@gh_end_) | Twitter までご連絡ください。

クレジット

写真:フリー素材ぱくたそ(www.pakutaso.com)様
鈴木・letor・太郎:model by 大川竜弥 様
Yさん:model by 大川竜弥 様

ろくろ系男子のフリー素材
https://www.pakutaso.com/20120432100post-1358.html

アイキャッチに使えそうなインタビュー中の風景のフリー素材
https://www.pakutaso.com/20180545128post-16090.html

WEB業界のろくろ回しのフリー素材
https://www.pakutaso.com/20120310082web.html

デスクの上でろくろ回しのフリー素材
https://www.pakutaso.com/20120313082post-1310.html

鋭い質問に一瞬たじろぐインタビュイーのフリー素材
https://www.pakutaso.com/20180548128post-16092.html