Slackの発言を特定のチャンネルに集約したい

# 目的
Slackで発言された，自社プロダクトに関する問題/疑問/確認点などをどんな些細なことであっても漏らさないようにしたい

# 問題と経緯
サービスの運営に支障が出るような重大な問題であれば直接のやりとりで対応するでしょうが，問題なのか仕様なのかハッキリしない/判断できない，実用上問題ないけど違和感がある．．．レベルの話はなかなか集め辛いものです．

問題/疑問/確認点を気軽に発言できるチャンネルを作ってそこで発言してもらう，というのも考えましたが，

- そもそも問題かどうかわからない，程度の意識のときに，わざわざチャンネルを移動しようと思うだろうか
- 全員が同じチャンネルに入る必要があるため，大人数のチャンネルで発言することに萎縮するのではないだろうか
- そもそもチャンネルが多い．．もう増やしたくない．．．
- 気軽にとか．．．演出だるくね．．．

みたいなことでもやもやした結果，もっと発言側の制約は少ないほうが良いよね，という結論に．
で，SlackのSlash CommandsとAWSのお力[^aws]を使ってごにょごにょやってみました．

# やったこと
## 概要
よくある，API GatewayとLambdaの組み合わせです．
ただし，今回はSlash Commandから別のチャンネルへ発言する必要があるため，それを別のLambdaとした関係で，Lambda->SNS->Lambda[^lambda]みたいな感じになってます．

## 設定
### Lambda1 - slack-slash
なんとなくJavaScriptです

```js
var aws = require('aws-sdk');
var sns = new aws.SNS({
  region: 'ap-northeast-1' //change to your region
});

var qs = require('querystring');

exports.handler = function(event, context) {
    //console.log('Received event:', JSON.stringify(event, null, 2));
    var body = event.postBody;
    var params = qs.parse(body);

    //var obj = parseAsObj(event.postBody);
    
    switch(params.command) {
        case '/ask':
            console.log('/ask');
            //InvocationType: 'Event',
            var json = {
                    from_channel: params.channel_name,
                    from_user: params.user_name,
                    text: params.text,
                    to_channel: '#questions',
                    to_user: 'もやもや承り君'
                };
            var sns_params = {
                Message: JSON.stringify({ default: JSON.stringify(json) }), /* required */
                MessageStructure: 'json',
                Subject: 'moyamoya',
                TopicArn: 'arn:aws:sns:ap-northeast-1:123456789012:topic-name'
            };
            sns.publish(sns_params, function(err, data) {
                if (err) {
                    console.log(err, err.stack); // an error occurred
                    context.fail('error');
                } else {
                    console.log(data);           // successful response
                    context.succeed({
                        "response_type": "ephemeral",
                        "text": "以下を送信しました．いつもご協力ありがとうございます :bow:",
                        "attachments": [
                            {
                                "text": params.text
                            }
                        ]
                    });
                }
            });
            break;
        default:
            context.fail('unknown command');
    }
};
```

`switch`の選択肢を加えれば別のSlash Commandをサクっと作れるようにしてあります．
Slash Commandで呼び出した先は通常3000ms以内にレスポンスを返さなければならないため，さっさとSNSに投げて終了するようにしています．

### lambda2 - post-slack
```js
var https = require('https');

/**
 * Pass the data to send as `event.data`, and the request options as
 * `event.options`. For more information see the HTTPS module documentation
 * at https://nodejs.org/api/https.html.
 *
 * Will succeed with the response body.
 * 
 * 
 */
exports.handler = function(event, context) {
    var params = JSON.parse(event.Records[0].Sns.Message);
    
    var options = {
        host: 'hooks.slack.com',
        port: 443,
        path: '/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX',
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
    };
    
    var response = { 
        channel: params.to_channel, 
        username: params.to_user, 
        text: params.from_user + " さんが " + params.from_channel + " で以下を投稿しました :ghost:", // 微妙に汎用性に欠けるので検討
        icon_emoji: ":first_quarter_moon_with_face:",
        attachments: [
            {
                text: params.text
            }
        ]
    };
    
    var req = https.request(options, function(res) {
        var body = '';
        console.log('Status:', res.statusCode);
        console.log('Headers:', JSON.stringify(res.headers));
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            body += chunk;
        });
        res.on('end', function() {
            console.log('Successfully processed HTTPS response');
            // If we know it's JSON, parse it
            if (res.headers['content-type'] === 'application/json') {
                body = JSON.parse(body);
            }
            context.succeed(body);
        });
    });
    req.on('error', context.fail);
    req.write(JSON.stringify(response));
    req.end();
};
```
こちらはSNSのSubscriberとして設定されるLambda
`event.Records[0].Sns.Message`にJSONが文字列で入っているので，パースしてやればよいです．

あとは，SlackのIncoming Web hookをよろしく設定してやります．

Lambda1とLambda2は現時点ではわけなくてもよかったのですが，将来的にはGithubのissueに自動登録とか，もっといろんなことをやらせてみたいと思ったときに対応しやすくしようと思ってこうなっています．

### API Gateway/SNSでLambdaを接続する
```
Slack(/ask) -(POST)-> API Gateway -> Lambda1 -> 発言者へフィードバック(ありがとう的な)
                                             -> SNS -> Lambda2 -(POST)-> Slack(特定のChannel)
```

# 結果
`/ask もやもや`と発言すると以下のようになり
![image](https://qiita-image-store.s3.amazonaws.com/0/18974/f71cda32-016f-4cb4-bb96-bda13fae84e3.png)

別の指定したChannelに集約されます
![image](https://qiita-image-store.s3.amazonaws.com/0/18974/0f8d33a6-736e-c165-9009-bcbf3d9ec21a.png)

開発中の画像なので名前とかテキトーですが，Slash Commandにしたことによって

- 発言場所(Channel)の制約が無くなり
- 誰にメンション付ける?とか気にしなくてよくなり

よりカジュアルに問題をあげてもらえれるようになればなぁと思っています．

---
[^aws]: API GatewayとLambda ~~で遊んでみたかっただけ~~ の調査の延長です
[^lambda]: Lambdaの中で別のLambdaは呼べないのかな?