Edited at

ElixirでDiscordのbotを作る

ElixirでDiscordのbot作るの楽しい~って思ったので書きました。

Elixir自体のインストールはこちらからどうぞ。

Discord botアカウントの作成等はこちらの記事が参考になります。


作るもの



  • !dice 2d6と送信するとダイスを振ってくれる

  • 結果は[(,区切りの各ダイス値)] 合計:(ダイスの合計値)のような形式で返ってくる

こんな感じのダイスロールbotを作ります。


作る

mix new discord_bot --sup

で新しいプロジェクトを作成します。

discord_botの部分は適宜変更してください。


ライブラリを導入する

今回はdiscord_alchemyを使用します。

Discord APIのラッパーライブラリです。

mix.exsを開き、depsを以下のように変更してください。


mix.exs

defp deps do

[
{:alchemy, "~> 0.6.1", hex: :discord_alchemy}
]
end

変更後、

mix deps.get

を実行することで、パッケージが取り込まれます。


pingされたら返事する

とりあえずbot界のHello, world!(?)を作りましょう。

最初に、config.exsにbotのトークンを書きます。


config/config.exs

config :discord_bot,

discord_token: "bot token"

次に、botの処理を書きます。

lib/discord_bot/application.exを書き換えます。


lib/discord_bot/application.ex

defmodule DiscordBot.Application do

use Application

defmodule Commands do
use Alchemy.Cogs

Cogs.def ping do
Cogs.say "pong!"
end
end

def start(_, _) do
token = Application.get_env(:discord_bot, :discord_token)
run = Alchemy.Client.start(token)
use Commands
run
end
end


これで!pingを送信したらpong!と返ってくるbotが完成しました!

mix run --no-halt

を実行することで、botが動作します。


ダイスを振る

さて、ついにダイスを振るコマンドを実装していきます。

Commandsモジュールで

Cogs.def コマンド名(引数) do

# 処理
end

とすることでコマンドを定義できます。

まずは6面ダイスを2個振った結果を返すコマンドCommands内に定義してみます。

Cogs.def dice do

dices = 1..2 |> Enum.map(fn _ -> 1..6 |> Enum.random end)
result = dices |> Enum.sum
Cogs.say("[#{dices |> Enum.join(",")}] 合計:#{result}")
end

2019-05-25_025646.png

できました!

やっていることは単純で、リスト[1, 2]のそれぞれにリスト[1, 2, ..., 6]からランダムに選ばれた値を突っ込んでるだけです。

Elixirのパイプライン演算子|>は、左の式の結果を右の式の第一引数に渡すものです。とてもべんりです。


m面ダイスをn個振る

最後に、!dice ndmの形式でダイスの個数、面数を指定できるようにします。

ついでに


  • 個数は1~100

  • 面数は1~1000

という制限もつけます。

Cogs.def コマンド名(引数)引数にはコマンド後の文字列が入ってくるので、それを利用してダイスの個数と面数を取得します。

後は、固定値だった26個数面数に置き換えるだけです。

Cogs.def dice(roll) do

case Regex.run(~r/\A(\d|[1-9]\d+)d(\d|[1-9]\d+)\z/, roll) do
[_, n, m] ->
num = n |> String.to_integer
face = m |> String.to_integer

cond do
num <= 0 || num > 100 ->
Cogs.say("個数は1以上、100以下にしてください")
face <= 0 || face > 1000 ->
Cogs.say("面数は1以上、1000以下にしてください")
true ->
dices = 1..num |> Enum.map(fn _ -> 1..face |> Enum.random end)
result = dices |> Enum.sum
Cogs.say("[#{dices |> Enum.join(",")}] 合計:#{result}")
end
_ ->
Cogs.say("ndmの形式で入力してください")
end
end

2019-05-25_065253.png

これでダイスロールbotのできあがりです!

引数なしの!diceコマンドを残しておくと、!diceした時は2d6を振り、!dice ndmした時はm面ダイスをn個振ってくれるのでべんりかもしれません。


さいごに

Elixirたのしい~~~!!!

文章を書くのは苦手なのでとてもつかれた。


おまけ

完成したlib/discord_bot/application.exの全文です。


lib/discord_bot/application.ex

defmodule DiscordBot.Application do

use Application

defmodule Commands do
use Alchemy.Cogs

Cogs.def dice do
dices = 1..2 |> Enum.map(fn _ -> 1..6 |> Enum.random end)
result = dices |> Enum.sum
Cogs.say("[#{dices |> Enum.join(",")}] 合計:#{result}")
end

Cogs.def dice(roll) do
case Regex.run(~r/\A(\d|[1-9]\d+)d(\d|[1-9]\d+)\z/, roll) do
[_, n, m] ->
num = n |> String.to_integer
face = m |> String.to_integer

cond do
num <= 0 || num > 100 ->
Cogs.say("個数は1以上、100以下にしてください")
face <= 0 || face > 1000 ->
Cogs.say("面数は1以上、1000以下にしてください")
true ->
dices = 1..num |> Enum.map(fn _ -> 1..face |> Enum.random end)
result = dices |> Enum.sum
Cogs.say("[#{dices |> Enum.join(",")}] 合計:#{result}")
end
_ ->
Cogs.say("ndmの形式で入力してください")
end
end
end

def start(_, _) do
token = Application.get_env(:discord_bot, :discord_token)
run = Alchemy.Client.start(token)
use Commands
run
end
end