Help us understand the problem. What is going on with this article?

Hubotに疲れた人のPython製bot"Errbot"入門

More than 3 years have passed since last update.

botといえばHubotが有名ですが、個人的にはJavascriptがそんなに得意でもないし、hubot-slack moduleもv3→v4で大幅に仕様が変わって追従できなくなり、Slackとの連携では稀に接続が切れてBot自体黙り込んでしまうというつらい状況で、ほとほと疲れてしまいました。一方、Python製のErrbotが良い出来なので紹介します。

なお、既に先人の方が入門については記載されているので、「HubotでできるアレはErrbotでできるのか」という観点で紹介したいと思います(あとメリット/デメリットも紹介します)。

Errbotを試しに導入する

これから紹介するErrbotとその機能については、以下にまとめたDockerコンテナで確認することができるようにしました。(プラグイン自体は、公式からほとんど引っ張ってきただけですが…)

https://github.com/tkit/errbot-plugin-example

上記READMEの通り、以下の通り実行すればTextモードで対話的に実行できます。

Errbotコンテナ起動
docker run -it --rm \
    --name err-text \
    -e BACKEND=Text \
    -e BOT_USERNAME="@testbot" \
    -e BOT_ADMINS="@tkit" \
    -e "TZ=Asia/Tokyo" \
    rroemhild/errbot

Slackで動かす場合は、 BACKEND=Slackとする必要があります。(Bot用のTOKENは別途取得してください)

Errbotコンテナ起動(Slack)
docker run --rm \
    --name err-slack \
    -e BACKEND=Slack \
    -e BOT_TOKEN=xoxb-...... \
    -e BOT_USERNAME="@testbot" \
    -e BOT_ADMINS="@tkit" \
    -e "TZ=Asia/Tokyo" \
    rroemhild/errbot

BOT_USERNAMEBOT_ADMINSは好きに書き換えてください。

起動後、以下のコマンドでプラグインをインストールしてください。

プラグインのインストール
!repos install https://github.com/tkit/errbot-plugin-example

機能について

基本的な命令

Errbotは、命令をmentionなどで送り込むのではなく、(デフォルトでは)!というPrefixを付与することによって発行します。

helloコマンド
[@tkit ➡ @testbot] >>> !hello
[@tkit ➡ @testbot] [␍]
Hello, world!
helpコマンドによるコマンドリファレンス
[@tkit ➡ @testbot] >>> !help
[@tkit ➡ @testbot] [␍]
All commands

 Backup
 Backup related commands.
• !backup - Backup everything.
 ChatRoom
 This is a basic implementation of a chatroom
• !room create - Create a chatroom.
• !room list - List chatrooms the bot has joined.
• !room destroy - Destroy a chatroom.
(snip)

mentionに反応して返事を返す

さて本題です。Hubotでいう robot.respond 相当のことです。
callback_mentionを使う方法と@re_botcmdデコレータを使う方法の2種類がありますが、後者の方が使いやすいでしょう。
(早速ですがTextモードでは動かないので、確認する場合はSlackなどを利用してください)

callback_mentionを使う

callback_mention関数を使うことで、非常に簡単に反応させることができます。

callback_mentionの例
from errbot import BotPlugin, botcmd

class Example(BotPlugin):

    # like robot.respond
    def callback_mention(self, message, mentioned_people):
        for identifier in mentioned_people:
            self.send(message.frm, 'User %s has been mentioned' % identifier)

        if self.bot_identifier in mentioned_people:
            self.send(message.frm, 'Errbot has been mentioned !')

コードを見ると分かる通り、callback_mentionは、別にBot向けのmentionに限らず あらゆるmention に反応します。そこから、Bot向けのmentionだけを条件で取ることができるので、そのときだけ反応させるようにすればよいわけです。

ちなみに、上記を動かすと、2つの条件がどちらも反応して無限ループします…。

respond.png

この方法がお勧めではないのは、簡単ではあるものの、あらゆる処理がcallback_mentionに集約されてしまうためです。見通しも悪くなります。この関数を使う場合は、Bot以外のmentionに反応させる場合などに限った方がよいでしょう。

@re_botcmdデコレータを使う

一方、関数に@re_botcmdデコレータを利用するこちらの場合は、パターンを正規表現で指定でき、よりrobot.respondに近い形で利用できます。

@re_botcmdの例
from errbot import BotPlugin, botcmd, re_botcmd
import re 

class Example(BotPlugin):

    # like hubot.respond
    @re_botcmd(pattern=r"^(([Cc]an|[Mm]ay) I have a )?cookie please\?$")
    def hand_out_cookies(self, msg, match):
        yield "Here's a cookie for you, {}".format(msg.frm)
        yield "/me hands out a cookie."

しかし、これだけではmentionに反応しません。mentionによっても反応させるためには、config中にある BOT_ALT_PREFIXESを指定すれば思った通りにいきます。

今回のケースでは、 BOT_ALT_PREFIXES=@testbot とすれば、mentionでも反応するようになります。

Errbotコンテナ起動(Slack)
docker run --rm \
    --name err-slack \
    -e BACKEND=Slack \
    -e BOT_TOKEN=xoxb-...... \
    -e BOT_ALT_PREFIXES="@testbot" \  # この行を追加
    -e BOT_USERNAME="@testbot" \
    -e BOT_ADMINS="@tkit" \
    -e "TZ=Asia/Tokyo" \
    rroemhild/errbot

ただし、今回のExampleでは、これとcallback_mentionが競合するので、これもまた無限ループになります。

hear.png

callback_mentionと比べると、こちらのほうが起動時にオプションが必要な分、ちょっとだけ面倒ですが、一度設定してしまえば気にならないですし、callback_mentionに全てまとめてしまうよりは後々やりやすくなるかと思います。

特定の発言に反応する

Hubotでいう robot.hear 相当のことです。
これも2種類の方法がありますが、後者のほうがよいでしょう。

callback_message関数を使う

単純にやると、以下の方法で callback_message を使えば、全ての発言を拾うので、その中から発動する条件だけを拾って動かすことができます。

callback_messageの例
from errbot import BotPlugin

class Example(BotPlugin):
    # like hubot.hear
    def callback_message(self, message):
        if message.body.find('cookie') != -1:
            self.send(
                message.frm,
                "What what somebody said cookie!?",
            )

これで問題はないのですが、全てのメッセージを拾うので、この callback_messageが肥大化しますし、一見何をするのか分かりづらくなります。(Classごと分けてしまってもいいでしょうけれど)

@re_botcmd デコレータ+prefixed=False指定を使う

mentionのときも紹介した@re_botcmdデコレータを使う方法もあります。デコレータのオプションでprefixed=Falseとすれば、プレフィックス(つまり@testbot相当のmention)なしで反応させることができます。

@re_botcmd+prefixed=Falseの例
from errbot import BotPlugin
import re

class Example(BotPlugin):

    # like robot.hear
    @re_botcmd( pattern=r"(^| )cookies?( |$)", prefixed=False, flags=re.IGNORECASE)
    def listen_for_talk_of_cookies(self, msg, match):
        """Talk of cookies gives Errbot a craving..."""
        return "Somebody mentioned cookies? Om nom nom!"

hear.png

発言を記憶する

Hubotでいうrobot.brain相当のコマンドです。といっても簡単で、self(つまり自分自身)に保存させるだけです。

@re_botcmd+prefixed=Falseの例
from errbot import BotPlugin

class Example(BotPlugin):

    # like robot.brain
    @botcmd
    def remember(self, msg, args):
        self['TODO'] = args

    @botcmd
    def recall(self, msg, args):
        return self['TODO']

Errbotが凄いのは、ちゃんと永続化してくれます。具体的には、BOT_DATA_DIRに定義している場所に保存しているようです。

https://github.com/errbotio/errbot/blob/5.1/errbot/config-template.py#L66

brain.png

他の保管先(手段)もPluginによって選べます。

定期的に実行する

Hubotでも標準ではできませんが、Errbotでも標準ではできません。
…が、attakeiさんのerrcronを使えば解決してしまいます!

https://github.com/attakei/errcron

まとめ

上記の通り、Hubotでできる主だったことはほとんどErrbotでもできます…というか、大抵のBotなら基本機能としてこの辺りはできるのでしょうね。
加えて、今回は紹介しませんでしたがErrbotはそのBot単体でかなり完成度が高いので、「ただErrbotがあるだけ」でいろいろできるのが強みであるように思います。

Errbotのいいところ

公式の情報が非常に充実している

もう公式だけ見ればいいんじゃないかというくらい公式の情報が充実しています。プラグインの開発や設定などは、とりあえず公式を一読すれば大まかにイメージがつきます。

Botだけで運用が全てできる

例えばプラグインのインストール・アップデートや、Bot自体の再起動、ログ参照、プラグインの削除までできてしまいます。Hubotなら、別途CI/CD環境が必要になりそうなところですが、ErrbotではBotさえ立ち上げてしまえば、Botとの対話の中でほぼ全て完結してしまいます。プラグインで何かしらのバグがあると、ちゃんとそのプラグインだけを切り離してくれたりもします。

これらの重要度の高い操作は実行できるユーザを絞ることもでき、よく考えられているなぁと思います。

Errbotのあまり嬉しくないところ

Errbotにも不満はあります。

名前がひどい

Errbotという名前で9割損している気がするのですが、どうなんでしょうかね…。Google検索しても、大抵はerr + botっていう結果で他の文献がヒットしている気がします。なんでこんな名前なんだろう…。

公式以外に情報が少ない

文献は公式にかなり充実していますし、大抵それを見れば解決するのですが、すこし欲張ったことをすると途端に情報がなくなる気がします。(そもそも利用者が少ない?)
なぜか動かないことが多く、結局のところはソースを死ぬほど読んで解決することが多かったのですが、自分だけなのだろうか…。

コードでBotの全てを定義できない

Hubotの場合は、Node.jsでHubotを作り、スクリプトをパスに配置して利用することになりますが、Errbotの場合はSlackなどのChatツールのなかで対話的にBotに命令を出し、Chatツールの中でプラグインをインストールしたり各種設定を行ったりするのが主として考えられているようです。

例えば、プラグインのインストールは !repos install <path>と命令を送り、Webserver機能を有効にするためには !plugin config <jsonで書いたconfig>を送り込む必要があります。

普通はそれでも問題ないのですが、これの問題点はコードでBotを定義できないことにあります。つまり、一度別の環境でBotを使おうとした時に、それをそのまま持っていっただけだは設定は完了せず、Botを起動後に追加で設定をしないとセットアップが完了しないという問題があります。(回避策はあるにはあるようですが、まだそれほど追えていません)

これはメリットでもありデメリットでもあって、毎回コードを修正したりしなくとも、チャットツールとBotさえあればほとんど解決してしまうので、良し悪しあるとは思います。

Chatツールとしての「共通」を目指している

例えばSlackにはInteractive Messageという機能がありますが、どうやら公式では対応予定がなさそうです。これは、Errbotが別にSlackだけのものではないからで、他のチャットツールを広くカバーする共通部分のみを対応するからのようです。

同様に、Slackのattachmentも、他のチャットツールとパラメータを同じにできる部分だけが使えるようになっていて、attachmentの幅広く豊かな表現を全て自由に使えるかというとそんなことはないです。これも、Backendのプラグインを改修すればいいのでしょうけれど、なかなかそこまでは…。

なので、チャットツールは特定のプロダクトのみで利用しようとしている場合(大抵そうだと思いますが)は、多少の作り込みが発生すると思ったほうがよさそうです。

最後に

いろいろ書きましたが、結局は一番使いやすい言語のBotを使えばいいんじゃないかなと思いました。Botが大流行している最近で、Hubotを必ずしも使わなければならないこともないかと思います。

そんな中で、Errbotは機能が充実していていい感じだったので、しばらく使ってみようかと思います。

tkit
Golang書いたりPython書いたりDevOpsしたりしているインフラエンジニアです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away