Laravelの通知ライブラリでslackボタンアクション機能を実装


[Laravel Advent Calendar 2019 - Qiita ](https://qiita.com/advent-calendar/2019/laravel) の 9 日目 の記事です。

Laravelの通知ライブラリを使ってボタン付きのslack通知を実装した記事を
下書きのままにしていたので書き上げることにしました..!

# Laravelの通知ライブラリ
## laravel5.8以降のslack通知機能に変化

通知クラスが外部ライブラリに変わりました！
[5.8以降のslack通知(公式)](
https://laravel.com/docs/5.8/notifications#slack-notifications)

composerを使って入れないと使えなくなりました。

```
composer require laravel/slack-notification-channel
```

ちなみに`v５．8`以前は`guzzle`を入れれば使える形でした..。
[5.8以前のslack通知(公式)](
https://laravel.com/docs/5.7/notifications#slack-notifications)

(実は`v5.7.16`以降から通知クラスは外部ライブラリ化されてますが、ドキュメントは5.8以降しか変更されてないので注意です。(今`5.7`をupdateするならメジャーアップデートしましょう。))

Nexmoの通知クラスと一緒にframwork内から削除されています。

[gitの差分](
https://github.com/laravel/framework/commit/57dedf149e84e323837b39d927e59d2688cf578d#diff-18387d95236a3fb07eeb0bab561f0cef)

## slack通知のボタンアクション機能とは？
公式のgifだと以下のような表示。
ボタンが付いていて押された後、元のメッセージを変えて1人しか押せなかったり、誰が押したか表示したり色んな機能が考えられそうです..!
![Example_6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207269/6d9901ea-1197-7ace-8439-f8b9b2f68331.gif)

[slack公式のボタン機能](https://api.slack.com/docs/message-buttons)

## 今までのslack通知クラスでボタン機能使えなかったの？
そうなんです！
当然、slack側のAPIには用意されていたので`guzzle`で直接叩いたり、`Custom Channels`を使って実装することは出来ましたが、Laravelが用意してくれている通知クラスでは使えませんでした。


# 環境

Laravelの開発環境をdockerで作るなら、@ucan-labさんの[Laravelの開発環境をDockerを使って構築する](https://qiita.com/ucan-lab/items/17c806973e69792ada99)がおすすめです。常に更新されてる！

slack側の設定は以下を参考に
・[Slack APIを使用してメッセージを送信する
](https://qiita.com/rubytomato@github/items/6558bfdb37d982891c09)

slackのボタンアクション通知の仕組みはこちらを参考にしました！
・[slackで単純なボタン付きメッセージを送る](https://qiita.com/masakabe/items/c47fc98e939475594842)

# 通信ライブラリを使って実装
さて、通信ライブラリを使って実装してみましょう。
[slack-notification-channel](https://github.com/laravel/slack-notification-channel)

通知したいチャンネルにIncoming Webhooksの設定をしてwebhookを取得します

Incoming Webhooksについてはこの記事を参考にしました。
[SlackのIncoming Webhooksを使い倒す] (https://qiita.com/ik-fib/items/b4a502d173a22b3947a0)

slack通知機能をまず作ります。この時、`.env` に設定したwebhookを`config`に書いておき、`routeNotificationForSlack` メソッドで指定します。

```php:SlackNotificationService.php
<?php
namespace App\Services;

use App\Notifications\SlackButtonMessage;
use App\Notifications\SlackSend;
use App\Notifications\SlackSendQuestion;
use Illuminate\Notifications\Notifiable;

class SlackNotificationService
{

    use Notifiable;

    /**
     * SlackチャンネルのWebhookURLを返す
     *
     * @return string
     */
    public function routeNotificationForSlack()
    {
        return config('services.slack.button');
    }


    /**
     * 送信メソッド
     * @param $message
     */
    public function send()
    {

        // 通知
        $this->notify(new SlackButtonMessage());

    }
}
```

上記の送信メソッドで呼ばれるslackボタン通知用のNotificationクラスを作成

```php:SlackButtonMessage.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;

/**
 * Slack通知クラス
 */
class SlackButtonMessage extends Notification
{
    use Queueable;
    private $sendMessage;
    private $title;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {

    }
    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['slack'];
    }

    /**
     * Slack通知処理
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\SlackMessage
     */
    public function toSlack($notifiable)
    {
        return (new SlackMessage)
            ->from('Test通知', ':face_vomiting:')
            ->content('これはテストです :face_vomiting:')
            ->attachment(function ($attachment) {
                $attachment->action('googleリンク','https://www.google.com/','primary');
            });
    }
}
```

出ました！
<img width="310" alt="スクリーンショット 2019-12-09 5.24.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207269/dc5b5a9b-2015-6417-0e9c-651674c47320.png">

そして積みました...。`slack-notification-channel`はボタン通知に対応していますが、ボタン押した際のアクションは単純なURLリンクしか設定できないようです！
`SlackAttachment.php` の所定の位置を見ると確かにそうなってます。

```php:SlackAttachment.php
    /**
     * Add an action (button) under the attachment.
     *
     * @param  string  $title
     * @param  string  $url
     * @param  string  $style
     * @return $this
     */
    public function action($title, $url, $style = '')
    {
        $this->actions[] = [
            'type' => 'button',
            'text' => $title,
            'url' => $url,
            'style' => $style,
        ];

        return $this;
    }
```

gifのようにボタン押下後に、元のメッセージに色々アクションを実装したい場合は、無理ですね。
あくまでも通知クラスのライブラリということですね。


# GuzzleでAPI実装
書いていきます!

```php:SlackButtonService.php

<?php
namespace App\Services;

use GuzzleHttp\Client;

class SlackButtonService
{
    public function postMessage( $message ) {

        $client = new Client([
            'headers' => [ 'Content-Type' => 'application/json' ]
        ]);

        $response = $client->post( config( 'services.slack.button' ),
            ['body' => $message]
        );
        logger($response->getBody());
        $data = json_decode( $response->getBody()->getContents());

        return $data;
    }
}
```

送信メッセージ出す所

```php:send.php

    private $slack;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct(
                                SlackButtonService $service)
    {
        $this->slack = $service;
    }

    public function send(){
        $message = "こんにちは！";

        $data = array(
            "text" => $message
        );

        $actions =
            [
                "id" => "1",
                "name" => "test",
                'type' => "button",
                'text' => "こんにちは！",
                "value" => "button_1",
            ];

        $data += [
            "attachments" =>
                [
                    [   "callback_id" => "test",
                        "fallback" => "More details...",
                        'actions' => [$actions],
                    ]
                ]
        ];

        $payload = json_encode($data);

        $res = $this->slack->postMessage($payload);
    }

```

こんな感じで出ますね！
<img width="237" alt="スクリーンショット 2019-12-09 7.37.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207269/2aef8e29-5383-a9c5-8548-de253a7ef352.png">



slackからのレスポンス処理を設定(Interactive MessagesでここのURLを設定しておくことでPOSTされます)

```php:web.php
Route::group(['prefix' => 'slack'], function()
{
    Route::post('/api/response', 'ApiController@getSlackResponse');
});
```


slackからのPOSTに対してcsrfトークンチェックを外す

```php:VerifyCsrfToken.php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * Indicates whether the XSRF-TOKEN cookie should be set on the response.
     *
     * @var bool
     */
    protected $addHttpCookie = true;

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
        '/slack/api/*',
    ];
}
```
レスポンス処理

```php:ApiController.php
    public function getSlackResponse(Request $request){

        $temp = json_decode($request['payload'],true);
        $temp['original_message']['text']=$temp['user']['name'] . 'が押しました！';
        return response($temp['original_message']);
    }
```

こんな感じで通知が来た後に...

<img width="237" alt="スクリーンショット 2019-12-09 7.37.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207269/32cf34bf-7b03-f8f9-86d9-afa84439e6b7.png">

ボタンを押すと...
<img width="211" alt="スクリーンショット 2019-12-09 7.37.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207269/2738ff35-8179-0ba3-ce69-a513cc260f04.png">

みたいに元のメッセージを変更したり出来ます！

slackを使っている会社では、slack連携による業務改善やエンゲージメントを高めるような施策が今後も増えてくると思います！
この記事が少しでも実装の助けになればと思います。