Matlab から Slack に通知を送る

# TL;DR
1. Get Webhook URL from https://slack.com/services/new/incoming-webhook.
    * You can get URL like `https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXXXXXX`
2. Download `slack_incoming.matlab` from my [gist](https://gist.github.com/gkhtnk/71e0570705944ff04adb6729822798a1).
3. Execute as follows on matlab.

    ```matlabsession
    >> url = 'https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXXXXXX';
    >> slack_incoming(url, 'text', 'text from matlab', 'channel', '#matlab');
    ```
4. You can get message from matlab
    ![slack_incoming.png](https://qiita-image-store.s3.amazonaws.com/0/159851/a816197f-dcbc-4841-db4b-7ab2da05f459.png)



# はじめに
研究者の間でも([Nature](https://doi.org/10.1038/541123a))活用の進むチャットアプリである [Slack](https://slack.com/)
そして、数値計算ソフトウェアとして利用される[MATLAB](https://jp.mathworks.com/products/matlab.html)
これらを組み合わせることで快適な解析環境の構築を手助けする。

ソースは以下で公開 (MIT License)
[gist](https://gist.github.com/gkhtnk/71e0570705944ff04adb6729822798a1)

必要なもの

* Matlab (R2017a student でテスト)
    * `webwrite` 関数が2015a で導入されたものなのでそれ以前の環境では動かない？)
* Slack ([Incoming Webhooks](https://api.slack.com/incoming-webhooks))


# 概要
Slack の Incoming Webhooks に対して、MATLAB の webwrite を用いて HTTPリクエストを送信する。


# 方法
使うまでにいくつかの手順が必要

## SlackのWebhook URLを取得する
http://qiita.com/vmmhypervisor/items/18c99624a84df8b31008
を参考にWebhook URLを取得する。

以下のようなアドレスを取得できる。
`https://hooks.slack.com/TXXXXX/BXXXXX/XXXXXXXXXX`
説明でもこのアドレスを用いるが、使用する際には各自のものに書き換える。


## payload を整形し、POSTする。
MATLABの関数[`webwrite`](https://jp.mathworks.com/help/matlab/ref/webwrite.html)を用いることで、指定したURLにデータをPOSTことができる。
ここではWebhook URLを指定する。

送信するpayloadの形式はSlackによってJSON形式のkey/valueペア方式と定められている。
以下の点に注意

* 改行文字を含まないこと
* 少なくとも`text`プロパティ(key)は含めること
    * ただし`attachments` プロパティがある場合、その値(value)内に`text`プロパティがあれば大丈夫なようだ(詳細は未確認)

例：`{"text": "Message from MATLAB", "channel": "#matlab",} `

MATLAB上でこの形式に合うように文字列を作成して送信すればよい。

# 関数の説明
以上の内容を実現するために、今回作成した関数では、payloadの整形を行う部分を実装した。
help の文章は以下の通り

```matlabsession
>> help slack_incoming
  response = slack_incoming(url, payload)
  url      : [char]  Webhook URL (you have to get Webhook URL from Slack)
  payload  : [char, struct]  payload must be JSON style string or struct
             # payload is a case when varagin is a single argument 
  varargin : [char/char] optional mode for define payload as key/value pair
  
  response : 'ok'        POST of payload was succeeded.
             otherwise   failed to POST
  
  see also Slack Incoming Webhooks <https://api.slack.com/incoming-webhooks>
```

## 使い方
**入力** は`url`と`payload`(実体は`varargin`)
**出力** はIncoming Webhook の応答(`'ok'`なら成功、それ以外は失敗)

`url` は SlackのWebhook URL

```matlabsession
>> url = 'https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXXXXXX';
```

`payload` としては、以下の3パターンを想定している。
:one: 整形済みの文字列 [char]

```matlabsession
>> # Use JSON str
>> payload = '{"text":"text from matlab","channel":"#matlab"}'

payload =

    '{"text":"text from matlab","channel":"#matlab"}'

>> slack_incoming(url, payload);
```


:two: JSON形式に従った構造体 [struct]

```matlabsession
>> # Use struct
>> payload = struct('text', 'text from matlab', 'channel', '#matlab')

payload = 

  フィールドをもつ struct:

       text: 'text'
    channel: '#matlab'

>> slack_incoming(url, payload);
```

:three: key/valueペアの可変長入力(偶数個の文字列) [char, char, ...]

```matlabsession
>> # Use key/value pair
>> slack_incoming(url, 'text', 'text from matlab', 'channel', '#matlab');
```

わざわざ書き分けたが、 :one:, :two:, :three: の違いはほとんどない
実際以下の3例は同じ結果になる

```matlabsession
>> slack_incoming(url, 'text', 'text from matlab', 'channel', '#matlab');
>> slack_incoming(url, struct('text', 'text from matlab', 'channel', '#matlab'));
>> slack_incoming(url, jsonencode(struct('text', 'text from matlab', 'channel', '#matlab')));
```

※ `jsonencode` は2016bからの実装なので、このコード中では使用していない。

## 注意
* keyの重複のチェックをしていないため、:three: のパターンでは、同じkeyが存在した場合に後の値によって書き換えられる。
* 基本的に送信する内容のチェックをしていないので、エラーが返ってきたら送信した内容を自分で確認する。

## おすすめの使い方
毎回 Webhook URL を入力するのは面倒なので以下のような関数を作成し、URLを関数のファイルに直接書き込んでおくと便利である。

以下のようなことも可能になる。

* 最初に定義したパラメータが入力に含まれる場合は上書きされるのでデフォルトの値として設定できる。
* 変更したくないパラメータは送信の直前に書き変えれば入力によって変更できない。

```matlab
function response = slack_example(text, varargin)
%% slack_example(text)

url = 'https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXXXXXX';
payload = struct('text', text);

% Default key-value(can be changed)
payload.channel = '#matlab';

if mod(nargin-1, 2)
  error('slack_example:KEY_VALUE_PAIR', 'options must be {key, value} pair');
end


for fi = 1:(nargin-1)/2
  if ~ischar(varargin{fi*2})
    error('slack_example:ALL_VALUE_MUST_BE_CHAR', 'All value must have char');
  end
  payload.(varargin{fi*2-1}) = varargin{fi*2};
end

% Restricted key-value (change is not allowed)
payload.username = 'MATLAB';

response = slack_incoming(url, payload);
```

実行例：
このメッセージは `#general` チャンネルに `MATLAB` というユーザー名で投稿される。


```matlabsession
>> slack_example('Hello Slack.', 'icon_emoji', ':ghost:', 'channel', '#general', 'username', 'GHOST');
```

![slack_example.png](https://qiita-image-store.s3.amazonaws.com/0/159851/136aa980-6973-3757-c0f3-8aff6da94ea3.png)
※デフォルトの チャンネルである`#matlab`が`#general`**変更されている**点と、ユーザー名である`MATLAB`が`GHOST`に**変更されていない**点に注目


# まとめ
* MATLABからSlackへメッセージを送信する関数を作成した。
* 実行には、Webhook URLを取得する必要がある。
* 自分用の関数を作成して使うとさらに便利になる。