Slack Appに挑戦(1) - WebhookとSlash Commandまで


「SlackBotにまじめに取り組む」の1回目
#この記事は
いままで、旧タイプのレガシートークンでIncoming Webhookを使ってSlackと連携していました。少し前にSlackのAppの仕様が変わったこともあって、レガシートークンではなくSlack Appの仕組みをちゃんと勉強してみることにしました。
今回は、Slack AppにあるIncoming Webhookとslash commandまでやってみます。Event APIは次の機会で。

##今回使う環境

+ Heroku
  + PHP 7.3
  + Laravel 6.0.3
  + Clear DB
+ Windows
  + PHP 7.2.11
  + Laravel 6.0.3
  + Maria DB  Ver 15.1

いつもどおり、基本的にはWindowsでの作業です。

Slash CommandでSlackからの呼びかけを受け取るために、インターネット上にHTTP/Sのサーバーを立てる必要があります。今回は、Laravel6系をHeroku上にデプロイして試しますが、Laravel6の新機能要素はゼロです。


#まず下準備

##Laravel 6 をインストール

```shell
C:\slalack-bot
composer create-project laravel/laravel --prefer-dist .

php artisan -V
Laravel Framework 6.0.3

#動作確認
php artisan serve
http://127.0.0.1:8000/

#いつもの画面が出ることを確認
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/11e98633-4f7e-d1a0-2545-2288063d6e93.png)

:sunny:

##Laravel をHerokuにデプロイ

C:\slalack-botに```Procfile```を作る

```bash:/slalack-bot/Procfile
web: vendor/bin/heroku-php-apache2 public/
```

Heroku上にアプリを定義してデプロイ
アプリ名は```slalack-bot```

```bash
#Heroku app 作成
heroku create slalack-bot
https://slalack-bot.herokuapp.com/ | https://git.heroku.com/slalack-bot.git

git init
Initialized empty Git repository in C:/slackbot/.git/

heroku git:remote -a https://git.heroku.com/slalack-bot.git
set git remote heroku to https://git.heroku.com/slalack-bot.git

git commit -m "application init"
git push heroku master
```

Laravelの```.env```の必要最低限をHerokuのconfigへ

```bash
heroku config:set APP_NAME="slalack-bot"
heroku config:set APP_ENV="production"
heroku config:set APP_KEY="base64:GlmfutBxM/hJJBdNuQyI267djR55srF15nJaoLV2QZo="
heroku config:set APP_DEBUG="true"
heroku config:set APP_URL="https://slalack-bot.herokuapp.com/"
```

https://slalack-bot.herokuapp.com/ にアクセスして、Laravelの画面が出ることを確認


#SlackにAppを登録
Slackに独自のワークスペースがある前提で進めます。

ブラウザでSlackのAPIページへいく
https://api.slack.com/apps?new_app=1

新規App画面が出るので、適当に入力し```Create App``ボタンを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/f0844e81-3965-cbc4-5144-df7ebe1c532d.png)

AppNameはあとで変えられるので、適当につけておく。いったんこんな感じで。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/a005f929-1d96-8b6a-ba1c-86d3169cb000.png)


##incoming webhookを試す
まずは、独自Appの動作確認のために```incoming webhook```を試してみます。ひと昔前はWebhookが単独でメニュー化されていましたが、最近は自分で定義したAppの一部に組み込まれるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/c6e02ea6-0aaa-abfd-7956-fe86f758d5a9.png)

Webhookは作成時にChannelを指定します。昔はメッセージを送るときに無差別に送るチャンネルを変更できましたが、最近は推奨されていないです。複数チャンネルに送りたい場合には、この手順を繰り返しチャンネルごとにWebhookを用意しないといけません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/ca476414-00e7-c925-93dd-a9dd45a68db7.png)

webhookを登録すると、こんな感じにメッセージの送信先URLが発行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/247ad28c-551e-c289-581e-158998ceba08.png)

このURLを誰かに知られるとワークスペースにメッセージ送られ放題になるので、気を付けましょう。

:warning: curlコマンドのワンライナーもコピーできますが、Windowsのコマンドプロンプトではシングルクオートの囲みがエラーになるので、ダブルクオートに書き換えます。（"の中の"は\でエスケープも）

```bash
curl -X POST -H "Content-type: application/json" --data "{\"text\":\"HelloWorld\"}" https://hooks.slack.com/services/aaa/bbb/ccc
```

指定したチャンネルにメッセージが届きます:sunny:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/986afc6f-29c3-cc58-3df4-9774460f2961.png)


##Slash Commandを試す
本命のスラッシュコマンドをやります。

###1.コマンドの受信用のHTTP/Sアプリを作る

artisanコマンドでコントローラを作り

```bash
php artisan make:controller SlalackBotController
Controller created successfully.
```

最初はdebugメソッドを用意。
内容は、(Slackから)受け取ったパラメータを全部ログに書き出しつつ、簡単なメッセージだけ返却するだけです。

```php:app/Http/Controllers/SlalackBotController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Log;

class SlalackBotController extends Controller
{
    public function debug(Request $request){
        \Log::info($request->all());

        $date = date('Y-m-d H:i:s');
        $responseText = 'debug method are called at ' . $date;
        return response()->json(['text'=>$responseText]);
    }
}
```

Slash CommandのレスポンスはJSONで戻す必要があります。 Laravelの```response()->json()```を使うと、PHPの配列を自動でJSONにしてくれて、レスポンスヘッダーに```application/json```が自動で付与されるので便利です。
https://readouble.com/laravel/5.5/ja/responses.html#json-responses


###2.routeの設定
web.phpではなくてapi.phpに登録します。この場合、```api/debug```がエンドポイントになります

```php:routes/api.php
Route::post('/debug', 'SlalackBotController@debug');
```

###3.ログの設定
HerokuにデプロイしたLaravelのログを見るのは面倒なので、SlackにLogを送ってしまう。最初に作ったIncoming Webhookにログが送られるようにします。(ここは必須ではないです)

```php:config/logging.php
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            #slackを追加
            'channels' => ['daily','slack'],
            'ignore_exceptions' => false,
        ],
```

.envファイルにWebhookのURLを設定する

```bash:.env
LOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/aaa/bbb/ccc
```

Heroku側にも設定を追加

```bash
heroku config:set LOG_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/aaa/bbb/ccc"
```

###4. 変更した内容をHerokuへデプロイ

```
git add .
git commit -m "log to slack"
git push heroku master
```

これで、https://slalack-bot.herokuapp.com/api/debug でPOSTを受けられるようになります。

###5.Slash Command として登録

Slack AppのBasicInformationからSlach Commandsへ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/afc2bda7-4c16-b86b-afa7-f87cccd1e16c.png)

Create New CommandからSlashコマンドの設定をする
Request URLは、先ほど作った```https://slalack-bot.herokuapp.com/api/debug```にしています。ここは各自環境により変わります。それ以外の部分はおまかせです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/d38e6673-bcfd-5a0a-5e2f-92b0b9ed83c7.png)

これでいったん準備完了です。

Slash Commandの登録完了したときに、permissionが変わったから再インストールするようにメッセージが出たら、メッセージに従って作業してください。

###6.いざ実行
Slackを開いて、スラッシュで呼びかけてみます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/948943b4-2b70-35f5-88e1-76f639672e99.png)

Botからのレスポンス。うまくいったっぽい:sunny:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/86814c2f-827b-fc69-1f57-df70a8b1e298.png)

正しく設定されていると、Botの裏で動いているLaravelのLogがSlackのWebhookにも届きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/c9e28a9e-caf3-7ef4-1e91-4bdb7f1a03b2.png)


スラコンのお試し、無事完了。

ログを見てみると、スラッシュコマンドに書いたメッセージは、Laravelの```$Request```の中の```text```から取れるってわけですね。PostメソッドでJSONが送られているはずなのに、LaravelではRequestのインスタンスで透過的に取れるようにしてくれます。


##Slash Commandを試す（2）
Slash Commandは3秒以内に200のレスポンスを返すことが求められています。もし、処理に時間がかかって返せない場合には、Slack CommandのPOST内容に含まれている```response_url```を覚えておいて、あとからHTTPでPOSTすれば、Slash Commandを送ってきた相手にメッセージが返せます。

URLの有効期限は30分以内、メッセージを送るのは5回までと決まっています。

ということで、```response_url```を使うバージョンを試してみました。

###1.Guzzleをインストール

LaravelからHTTPSアクションを起こすので、Guzzleをインストール

```
composer require guzzlehttp/guzzle
```

###2.コントローラにdelayメソッドを追加

Guzzleクライアントは、```json```という名前のキーでPHPの配列を渡すと、自動的にリクエストヘッダーに```application/json```をセットし、BodyにJSON文字列を渡してくれるので、実装がとても簡単です。
http://docs.guzzlephp.org/en/stable/request-options.html#json

```php:app/Http/Controllers/SlalackBotController.php
    public function delay(Request $request){

        $responseUrl = $request->input('response_url');
        $text = $request->input('text');
        \Log::debug($responseUrl);
        \Log::debug($text);
        
        $guzzle = new \GuzzleHttp\Client();
        $option = [];
        $option['debug']=false;
        $option['verify']=false;
        $option['http_errors']=false;

        ##5回以上はエラーになるはずなので6回
        for( $i=0; $i<6; $i++){
            $option['json']=['text'=> "$text : reply from bot ($i)" ];
            $response = $guzzle->post($responseUrl, $option);
            $body = $response->getBody();
            $data = (String)$body;

            if( $response->getStatusCode() != 200 ){
                \Log::error($data);
                break;
            }
        }

        $date = date('Y-m-d H:i:s');
        $responseText = 'Direct reply at ' . $date;
        return response()->json(['text'=>$responseText]);
    }
```

上で作ったdelayメソッドAPIとして登録する。

```php:routes/api.php
Route::post('/delay', 'SlalackBotController@delay');
```

###3.Slack Appに追加
delayメソッドを新しくスラッシュコマンドに追加で登録します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/600f8a3a-e0fc-5a42-0d6f-647ab007a868.png)

###4.いざ実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/3dd0397d-df11-22f9-86fb-28e779604b7e.png)

6回目の呼び出しは404のエラーになり、Log::errorで書き出ししたものもSlackに無事到着。その後にDirectなレスポンスも届きました。


#Laravelらしく、Queueに入れてから処理する

ここまでの実験で、何をやる必要があるかは理解できました。
せっかくLaraveで作っているので、LaravelのQueueの仕組みを使ってJobを実行して、Slackに返信を行うようにしてみます。

やることは、

1. Slashコマンドを受ったら、メッセージの内容をDBに保存し
2. Laravelのキューに入れ、Slackにはいったんメッセージを返す
3. キューに入ったジョブを実行し、Slackを呼び出しした人へ返答する


##1.データベース設定
Slackからのデータを一時的に保存する必要があるので、HerokuでClearDBのAddonを入れます。あわせてClearDBの接続パラメータをHerokuのConfigに必要なパラメータを設定しておきます。

```bash
heroku config:set DB_CONNECTION="mysql"
heroku config:set DB_HOST="us-cdbr-iron-east-02.cleardb.net"
heroku config:set DB_PORT="3306"
heroku config:set DB_DATABASE="heroku_xxxxx"
heroku config:set DB_USERNAME="bd048xxxxxx"
heroku config:set DB_PASSWORD="xxxx"

#これも必要なので一緒に
heroku config:set QUEUE_CONNECTION="database"
```

##2.Job Queueの準備
Jobを保存する仕組みを構築します。Laravelの基本的な仕組みをそのまま使います。

###必要なテーブルの準備

```
php artisan queue:table
Migration created successfully!
```

database/migrationsにファイルができます。不要なファイルもできるので消しておきます。

+ 2014_10_12_000000_create_users_table.php (いらない）
+ 2014_10_12_100000_create_password_resets_table.php　（いらない）
+ 2019_08_19_000000_create_failed_jobs_table.php
+ 2019_09_22_215540_create_jobs_table.php

MySQLを使っている場合で、jobsテーブルの`up`メソッドの`queue`カラムで以下のエラーが出たら、migrationのファイルを編集して凌ぎます。

>Illuminate\Database\QueryException  : SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table \`jobs\` add index \`jobs_queue_index\`(\`queue\`))

今回は、indexを作らないように修正して避ける

```php:2019_09_22_215540_create_jobs_table.php
    public function up()
    {
        Schema::create('jobs', function (Blueprint $table) {
            $table->bigIncrements('id');

            //ここ編集
            //$table->string('queue')->index();
            $table->string('queue');

            $table->longText('payload');
            $table->unsignedTinyInteger('attempts');
            $table->unsignedInteger('reserved_at')->nullable();
            $table->unsignedInteger('available_at');
            $table->unsignedInteger('created_at');
        });
    }
```

###Jobクラス作成

```
php artisan make:job ProcessSlackMessage
Job created successfully.
```

Jobs/ProcessSlackMessage.phpが作られるので、Slackにメッセージを送るように実装をします。


```php:Jobs/ProcessSlackMessage.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

use Log;

class ProcessSlackMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $slackMessage = null;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(\App\SlackMessage $slackMessage)
    {
        //
        $this->slackMessage = $slackMessage;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $id = $this->slackMessage->id;
        \Log::debug('handling job ' . $id);

        $guzzle = new \GuzzleHttp\Client();
        $option = [];
        $option['debug']=false;
        $option['verify']=false;
        $option['http_errors']=false;

        $message = 'ID:' . $id . "\n" . $this->slackMessage->text . 'への返信です';
        $option['json']=['text'=> $message ];

        $response = $guzzle->post($this->slackMessage->response_url, $option);
        $body = $response->getBody();
        $data = (String)$body;

        if( $response->getStatusCode() != 200 ){
            \Log::error($data);
        }else{
            $this->slackMessage->delete();
            \Log::debug('SlackMessage ID ' . $id .' was deleted.');
        }
    }
}

```

コンストラクタでSlackMessageを受けとるようにしておくと、Queueに入れるときに関連づけたSlackMessageのインスタンスが渡ってきます。あとは`handle`メソッドで、SlackMessageに入っている情報でお仕事をし、SlackのAPIをコールすると返答ができます。

##3. SlackMessageのモデル作成
SlackのAPIで渡された値をそっくりそのまま入れておくテーブルを作ります

###migrationファイルを作成
```bash
php artisan make:migration create_slack_messages_table
Created Migration: 2019_09_22_221816_create_slack_messages_table
```

database/migrations/2019_09_22_221816_create_slack_messages_table.phpが作られる。`upメソッド`を以下のように実装して、テーブルが作られるようにする。

```php:database/migrations/2019_09_22_221816_create_slack_messages_table.php
    public function up()
    {
        Schema::create('slack_messages', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('token');
            $table->string('team_id');
            $table->string('team_domain');
            $table->string('channel_id');
            $table->string('channel_name');
            $table->string('user_id');
            $table->string('user_name');
            $table->string('command');
            $table->string('text');
            $table->string('response_url');
            $table->string('trigger_id');
            $table->timestamps();
        });
    }
```

###SlackMessageモデルを作成

```bash
php artisan make:model SlackMessage
Model created successfully.
```

SlackMessageモデルクラスは```fillable```にカラム名を宣言するだけ

```php:SlackMessage.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class SlackMessage extends Model
{
    protected $fillable = [
        'token', 'team_id', 'team_domain', 'channel_id', 'channel_name',
        'user_id','user_name','command','text','response_url','trigger_id'
    ];

}

```

##4.Slackから受け取るAPI

SlalackBotControllerにqueueメソッドを作ります。Slackからのリクエスト内容をSlackMessageテーブルに保存してJobへDispatchする。5行だけとは。

```php:App/Http/Controllers/SlalackBotController.php

    public function queue(Request $request){
        $slackMessage = new \App\SlackMessage();
        $slackMessage->fill($request->all());
        $slackMessage->save();

        \App\Jobs\ProcessSlackMessage::dispatch($slackMessage);
        return response()->json(['text'=>'saved ' . $slackMessage->id]);
    }

```

APIとして呼び出せるよう設定

```php:routes/api.php
Route::post('/queue', 'SlalackBotController@queue');
```

###Slack Appに登録
Appに新たにスラッシュコマンド`/queue`を登録します。もう手慣れた感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/574b79ed-bd77-dfad-16bc-0ff96507f81c.png)

##5.Herokuにデプロイ
###GIT

```
git add .
git commit -m "implements queue"
git push heroku master
```

###テーブルのmigration

```
heroku run php artisan migrate
Running php artisan migrate on ⬢ slalack-bot... up, run.5569 (Free)
**************************************
*     Application In Production!     *
**************************************

 Do you really wish to run this command? (yes/no) [no]:
 > yes

Migration table created successfully.
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0 seconds)
Migrating: 2019_09_22_215540_create_jobs_table
Migrated:  2019_09_22_215540_create_jobs_table (0 seconds)
Migrating: 2019_09_22_221816_create_slack_messages_table
Migrated:  2019_09_22_221816_create_slack_messages_table (0.01 seconds)
```

###テーブル確認

Windowsからmysqlコマンドで接続

```
mysql -u (user) -p(pass) -h xxx-cleardb.net heroku_74cea6efa72d039

MySQL [heroku_74cea6efa72d039]> show tables;
+----------------------------------+
| Tables_in_heroku_74cea6efa72d039 |
+----------------------------------+
| failed_jobs                      |
| jobs                             |
| migrations                       |
| slack_messages                   |
+----------------------------------+
4 rows in set (0.18 sec)

MySQL [heroku_74cea6efa72d039]> desc jobs;
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| queue        | varchar(255)        | NO   |     | NULL    |                |
| payload      | longtext            | NO   |     | NULL    |                |
| attempts     | tinyint(3) unsigned | NO   |     | NULL    |                |
| reserved_at  | int(10) unsigned    | YES  |     | NULL    |                |
| available_at | int(10) unsigned    | NO   |     | NULL    |                |
| created_at   | int(10) unsigned    | NO   |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+
7 rows in set (0.18 sec)

MySQL [heroku_74cea6efa72d039]> desc failed_jobs;
+------------+---------------------+------+-----+-------------------+----------------+
| Field      | Type                | Null | Key | Default           | Extra          |
+------------+---------------------+------+-----+-------------------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL              | auto_increment |
| connection | text                | NO   |     | NULL              |                |
| queue      | text                | NO   |     | NULL              |                |
| payload    | longtext            | NO   |     | NULL              |                |
| exception  | longtext            | NO   |     | NULL              |                |
| failed_at  | timestamp           | NO   |     | CURRENT_TIMESTAMP |                |
+------------+---------------------+------+-----+-------------------+----------------+
6 rows in set (0.18 sec)

MySQL [heroku_74cea6efa72d039]> desc slack_messages;
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| token        | varchar(255)        | NO   |     | NULL    |                |
| team_id      | varchar(255)        | NO   |     | NULL    |                |
| team_domain  | varchar(255)        | NO   |     | NULL    |                |
| channel_id   | varchar(255)        | NO   |     | NULL    |                |
| channel_name | varchar(255)        | NO   |     | NULL    |                |
| user_id      | varchar(255)        | NO   |     | NULL    |                |
| user_name    | varchar(255)        | NO   |     | NULL    |                |
| command      | varchar(255)        | NO   |     | NULL    |                |
| text         | varchar(255)        | NO   |     | NULL    |                |
| response_url | varchar(255)        | NO   |     | NULL    |                |
| trigger_id   | varchar(255)        | NO   |     | NULL    |                |
| created_at   | timestamp           | YES  |     | NULL    |                |
| updated_at   | timestamp           | YES  |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+
14 rows in set (0.18 sec)
```


##6.いざ実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/16e3f8ce-12f1-e2fb-4c43-56661822b59e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/b14a80f5-96b1-da0d-b8ed-fc896d9264df.png)

id=42として登録したようだ。

###データ確認

**slack_messagesテーブル**

id=42を検索

```
MySQL [heroku_74cea6efa72d039]> select * from slack_messages where id=42\G
*************************** 1. row ***************************
          id: 42
       token: 1YxL3GCEx7R9CFgal0LvIDpK
     team_id: TDWRKFV0S
 team_domain: kanaxx
  channel_id: CDWNT347M
channel_name: channel2
     user_id: UDX2143CM
   user_name: kanaxx-user
     command: /queue
        text: キューへ入れー！！
response_url: https://hooks.slack.com/commands/TDWRKFV0S/756504982386/NMTbFokC89DGibmiqvTrxnaY
  trigger_id: 762861257665.472869539026.78157a4a62d1c306e2fd26f2c6c27257
  created_at: 2019-09-23 11:12:34
  updated_at: 2019-09-23 11:12:34
1 row in set (0.19 sec)
```

入ってる！

**jobsテーブル**

jobsテーブルも確認

```
MySQL [heroku_74cea6efa72d039]> select * from jobs\G
*************************** 1. row ***************************
          id: 32
       queue: default
     payload: {"displayName":"App\\Jobs\\ProcessSlackMessage","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"delay":null,"timeout":null,"timeoutAt":null,"data":{"commandName":"App\\Jobs\\ProcessSlackMessage","command":"O:28:\"App\\Jobs\\ProcessSlackMessage\":9:{s:15:\"\u0000*\u0000slackMessage\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":4:{s:5:\"class\";s:16:\"App\\SlackMessage\";s:2:\"id\";i:42;s:9:\"relations\";a:0:{}s:10:\"connection\";s:5:\"mysql\";}s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:5:\"delay\";N;s:10:\"middleware\";a:0:{}s:7:\"chained\";a:0:{}}"}}
    attempts: 0
 reserved_at: NULL
available_at: 1569204754
  created_at: 1569204754
1 row in set (0.19 sec)
```

よくわからないけど、1個だけ何か入ってる！


###Queueの処理
Queueに溜まっているJobを一つ処理してみます。```artisan```の```queue:work```で実行します。

```
heroku run php artisan  queue:work --once

Running php artisan queue:work --once on ⬢ slalack-bot... up, run.7817 (Free)
[2019-09-23 11:17:49][32] Processing: App\Jobs\ProcessSlackMessage
[2019-09-23 11:17:49][32] Processed:  App\Jobs\ProcessSlackMessage
```

無事終了。

うまくいくと、この後にSlackにメッセージが届きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/843de708-f37e-407e-5e84-8b2375f542cc.png)


届いた！:sunny:

:warning: 本番環境では、```php artisan queue:work```でバックグラウンド処理をするか、ジョブスケジューラなどから、定期的にartisanコマンドを叩く必要があります。
https://readouble.com/laravel/6.0/ja/queues.html#running-the-queue-worker

###データの再確認

QueueとSlackMessageを処理したあとのデータの確認

```
MySQL [heroku_74cea6efa72d039]> select * from slack_messages where id=42\G
Empty set (0.20 sec)

MySQL [heroku_74cea6efa72d039]> select * from jobs\G
Empty set (0.20 sec)
```

無事、両方とも空っぽですね。すばらしい

#まとめ
SlackのAPIの中で、Webhookで何かを一方的に送り付ける方法と、Slashコマンドできっかけをユーザからもらって反応する方法を試しました。まぁ、返答している内容がイマイチなので、もうちょっと楽しくしたいところです。本当は、SlackコマンドをもらってからHistoryを読み出したり、＠コマンドで話しかけられたいのですが、それはもうちょっと勉強と実験が必要なようです。

Queueに入れて遅延実行がこんなに簡単にできるとは思わなかった。今回分かったことは、やっぱりLaravelが便利だということです。

#参考資料
Slack Botの種類と大まかな作り方
https://qiita.com/namutaka/items/233a83100c94af033575

Slack API - Slash Commands
https://api.slack.com/slash-commands

MySQLのインデックスサイズに767byteまでしかつかえない問題と対策
https://blog.e2info.co.jp/2017/04/17/mysql%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AB767byte%E3%81%BE%E3%81%A7%E3%81%97%E3%81%8B%E3%81%A4%E3%81%8B%E3%81%88%E3%81%AA%E3%81%84/