processingのコードを送信したらgifかpngで出力してくれるslackbotを作った話

# はじめに
深夜テンションで一気に書ききったのでたくさん修正するかもしれません。
この記事ではslackbotを起動に漕ぎ着けるまでの解説を行います。デプロイして運用！とかは他の記事を見ながら頑張っていただけたら...
この記事で使用してるコードは[こちら](https://github.com/kota-shiokara/processing-slackbot)に上げてます。

なお、この記事は様々な説明を省略してたりするのでご了承ください。

# 本文
## 環境など(筆者の環境なのであくまでも目安に)
- macOS Catalina 10.15.4
- Slack
- processing-java
- Python 3.7.4(Python3系なら多分大丈夫だと思います)
  - [slackbot](https://github.com/lins05/slackbot) 0.5.6
  - [Pillow](https://pillow.readthedocs.io/en/stable/) 7.1.2

pipでslackbot、Pillowライブラリをインストールしておきましょう。(場合によっては```pip3```)
<div>

```bash

$ pip install slackbot
$ pip install Pillow

$ pip list # インストール済みのライブラリの確認
```

また、processing-javaのインストールもしてください。processingの導入は[この記事](https://qiita.com/greatnekochan/items/010237ab3313757d0b0b)がおすすめです。
[processingのダウンロード](https://processing.org/)し、起動、ツールバーから「ツール」>「processing-javaをインストール」を選択でインストールできるはずです。

</div>

## botの作成
以下のサイトからbotを作成してください。なお作成するワークスペースを間違えないよう注意しましょう。
https://my.slack.com/services/new/bot
(本来こちらは推奨されておらず、scopeの設定などめんどくさいものをやらなくてはいけませんが今回はこれで行きます。)

こちらのユーザー名にbotの名前を入れて「ボットインテグレーションを追加する」を押してください。
<img width="978" alt="slackでの設定.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/442889/58285610-f7ac-6271-398e-311818ea783e.png">

表示された「APIトークン」はコピペなりなんなりでメモしておきましょう。名前、アイコンなどは好きにカスタマイズしましょう。
完了したらページ一番下のボタンを押して設定の保存をしてください。
以降は```https://<ワークスペースのURL>/apps```のページで「bots」と調べて出てくるページで編集及びトークンの確認ができます。

## botを動かす準備
下記のようなディレクトリを用意しましょう。[こちら](https://github.com/kota-shiokara/processing-slackbot)をcloneしてくると大体の作業を飛ばせます。(その場合(※)のフォルダ、ファイルを用意してください！)

```
slackbot         # プログラムをまとめるディレクトリ。任意の文字列
├─ run.py        # bot起動のためのメインファイル
├─ slackbot_settings.py   # botに関する設定を書くファイル(※)
├─ plugins                # botの機能はこのディレクトリに追加する
   ├─ __init__.py         # モジュールを示すためのファイル。空でOK
   └─ func.py       # 機能を書くファイル。任意の文字列
└─ sketch (※)
   └─ sketch.pde    # 一旦書き込むスケッチファイル。空でOK(※)
```
以下のコードを書いてください。
<details>
<summary>```run.py```のコード</summary>
<div>

```py:run.py
from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print('start slackbot')
    main()
```

</div>
</details>

<details>
<summary>```slackbot_settings.py```のコード</summary>
<div>

```py:slackbot_settings.py
API_TOKEN = "<メモしたAPIトークン>"
DEFAULT_REPLY = "<デフォルトで返したい任意の文字列>"
PLUGINS = ['plugins'] # botの機能を書いてるコードがあるディレクトリのお名前
```

</div>
</details>

<details>
<summary>```func.py```のコード</summary>
<div>

```py:func.py
import glob
import os
import os.path
import subprocess
import sys
import time

from PIL import Image

from slackbot.bot import default_reply  # 該当する応答がない場合に反応するデコーダ
from slackbot.bot import listen_to  # チャネル内発言で反応するデコーダ
from slackbot.bot import respond_to  # @botname: で反応するデコーダ


@listen_to('!exit')  # exitコマンド:プロセスを終了する
def kill_process(message):
    message.send('See you!')
    print('process finished')
    os._exit(10)  # プロセスの強制終了


@listen_to('!output (.*)')
def output(message, arg):  # argはオプション
    # 送信されたテキストを整形
    tmp = message.body['text']
    tmp = tmp.replace("&lt;", "<")
    tmp = tmp.replace("&gt;", ">")

    # pde上書き用の文字列に整形
    pdeCode = shaping_code(tmp.strip("!output " + arg + "\n"), arg)
    message.send('wait...')

    # pdeに上書き
    print(pdeCode)
    with open('sketch/sketch.pde', 'w') as f:
        f.write(pdeCode)

    # processingの実行
    sketch_path = '--sketch=' + os.path.abspath('./sketch')
    cp = subprocess.run(
        ['processing-java',  sketch_path, '--run'])
    if cp.returncode != 0:  # processingの実行失敗時の処理
        message.send('Run is failed. Please send your sketch again.')
        return

    upload_sequence(message, arg)  # upload処理


def shaping_code(code, option):
    if option == '--png':  # pngオプション
        pictFunc = "  if((frameCount <= 15) && (frameCount % 15 == 0)) saveFrame(\"####.png\");\n  else if(frameCount > 15) exit();"
    elif option == '--gif':  # gifオプション
        pictFunc = "  if((frameCount <= 300) && (frameCount % 15 == 0)) saveFrame(\"####.png\");\n  else if(frameCount > 300) exit();"
    else:  # デフォルトではpngで返す
        pictFunc = "  if((frameCount <= 15) && (frameCount % 15 == 0)) saveFrame(\"####.png\");\n  else if(frameCount > 15) exit();"
    return code.replace("void draw(){", "void draw(){\n" + pictFunc)


def upload_sequence(message, option):
    if option == '--png':  # pngオプション
        message.channel.upload_file(
            fname="sketch/0015.png", fpath="sketch/0015.png")
        for p in glob.glob('sketch/*.png'):
            if os.path.isfile(p):
                os.remove(p)
    elif option == '--gif':  # gifオプション
        time.sleep(6)
        file_list = sorted(glob.glob('sketch/*.png'))
        images = list(map(lambda file: Image.open(file), file_list))
        images[0].save('sketch/output.gif', save_all=True,
                       append_images=images[1:], duration=400, loop=0)
        if os.path.exists('sketch/output.gif'):
            message.channel.upload_file(
                fname="sketch/output.gif", fpath="sketch/output.gif")
            for p in glob.glob('sketch/*.png'):
                if os.path.isfile(p):
                    os.remove(p)
            os.remove('sketch/output.gif')
    else:  # デフォルトではpngでupload
        message.channel.upload_file(
            fname="sketch/0015.png", fpath="sketch/0015.png")
        for p in glob.glob('sketch/*.png'):
            if os.path.isfile(p):
                os.remove(p)
```

</div>
</details>

## bot起動
準備が完了したら実際にbotを起動してみましょう。
slackbotディレクトリに移動して、

```bash

$ python3 run.py
```
を叩いてプログラムを実行しましょう。

- pngの場合

<img width="500" alt="png.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/442889/f57716d2-e384-45cd-1851-9c783576a1f2.png">
このように```!output --png```とつけてコードを送信するとpngで返してくれるbotが動けば勝ちです。
なお、出来上がったpngは自動で消去されるようにしているので、容量の圧迫が〜などは気にしないで大丈夫です。

- gifの場合

![gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/442889/8e01c18e-c197-30f0-f70e-b7c5f8cfac45.gif)
このように```!output --gif```とつけてコードを送信し、gifで返してくれれば勝ちです。
こちらも同じようにgif、png共に消去されます。

# 終わりに
拙い説明ですが以上で起動までの説明を終わらせていただきます。
subprocessでコマンドを叩いたり、正規表現使って色々なコマンドを作成できそうで面白かったです。
やる気が出れば使用したメソッドの解説記事やbot運用記事など書いてみようかと思います。
デプロイをまだやってないので未知数ですが...~~(インフラって何)~~

# 参考記事
- [Herokuでslackbot(python)を動かしてみた【おみくじBOT】](https://qiita.com/akabei/items/ec5179794f9e4e1df203)
- [PythonのslackbotライブラリでSlackボットを作る](https://qiita.com/sukesuke/items/1ac92251def87357fdf6)
- [時差出勤始まったので退勤時刻を教えてくれるBotを作った](https://qiita.com/Sion1073/items/2c39ab27128ba4f38514)

# 追記
- 2020-06-16 cfg.pyの廃止に成功したので関連するものを消去しました。