slackのログをAWS(DynamoDB)にためて、検索できるようにした

**> chatOps的な話ではなく、slack のことでお願いします。**

すみません、書く直前に知りました。。
でも、カレンダー全然埋まってないし、chatOps的なこと書いてる人他にもいるので強行します。
ごめんなさい。

# はじめに
slack【有料版】使ってますか？でしたらこの記事はあなたには特に必要ないです。

# 無料版の制約
有料版にどんな素晴らしい機能があるのかよくわかりませんが、無料版で特に不自由していません。
ただ、1つだけ、過去ログが消えてしまう、という点を除いては。

# どうするか？
slackでの発言が消えてしまう前に、全部DBに溜め込んで、そこから検索させればいいじゃない。

# 構成
slackでのHubotとの連携の記事は腐るほどあるので、省きます。
構成は、こんな感じでやりました。

- 理想
    - client > 自鯖(Go aws-sdk) > DynamoDB <> CloudSearch ← AWS Management Console から検索
- 現在
    - client > 自鯖(Go aws-sdk) > DynamoDB ← aws-sdk(javascript) で検索

EC2使えばいいじゃない、という話ですが、自前の環境があったのでそちらを使いました。
golangは書いてみたかったので選択しました。

本当は理想の通り、DynamoDB→CloudSearchにデータを入れて、AWSのManagementConsoleから全文検索させてかったのですが、
DynamoDB→CloudSearchにデータを定期的に入れる方法と、いまいちCloudSearchの使い方を理解できておらず
結局のところ、DynamoDBをLIKE検索する方法で落ち着いています。

**slackでの発言をDynamoDBにInsert**
(slackでの発言を受信して、↓のプログラムに渡してます。hubotスクリプトは省きます)

```go:dynamodb.go
package main

// Import the AWS SDK for Go
import (
    "log"
    "flag"
    "time"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

/**
 * Don't hard-code your credentials!
 * Export the following environment variables instead:
 *
 * export AWS_ACCESS_KEY_ID='AKID'
 * export AWS_SECRET_ACCESS_KEY='SECRET'
**/

func main() {

	var account = flag.String("a", "NULL", "help message for a")
	var date_time = flag.String("t", time.Now().Format("2006-01-02 15:04:05"), "help message for t")
	var channel = flag.String("c", "", "help message for c")
	var message = flag.String("m", "", "help message for m")
	flag.Parse()

	svc := dynamodb.New(session.New(&aws.Config{Region: aws.String("ap-northeast-1")}))
	input := &dynamodb.PutItemInput{
		TableName: aws.String("slack_log"),
		Item: map[string]*dynamodb.AttributeValue{
			"id": &dynamodb.AttributeValue{
				S: aws.String(*account),
			},
			"created": &dynamodb.AttributeValue{
				S: aws.String(*date_time),
			},
			"channel": &dynamodb.AttributeValue{
				S: aws.String(*channel),
			},
			"message": &dynamodb.AttributeValue{
				S: aws.String(*message),
			},
		},
	}

	_, err2 := svc.PutItem(input)
	if err2 != nil {
		log.Println(err2)
		return
	}
}
```

**DynamoDBをWebブラウザから検索**

- 検索条件
    - チャンネル名(=)
    - 発言(LIKE)
    - 発言日時(BETWEEN)

```javascript:dynamodb.js
function search_slack() {
    var attrValues = [];
    var attrNames = [];
    var filter = '';

    var channel = document.getElementById("search_channel").value;
    var message = document.getElementById("search_message").value;
    var start = document.getElementById("search_date_start").value;
    var end = document.getElementById("search_date_end").value;

    var dynamodb = new AWS.DynamoDB({
        dynamodb: '2012-08-10',
        region: 'ap-northeast-1',
        accessKeyId: 'hogehoge',
        secretAccessKey: 'fugafuga',
    });

    var params = {
        TableName: 'slack_log',
    };

    if(channel != '') {
        attrNames['#channel'] = 'channel';
        attrValues[':val1'] = {S: channel};
        filter = 'contains (#channel, :val1)';
    }

    if(message != '') {
        attrNames['#message'] = 'message';
        attrValues[':val2'] = {S: message};
        if(filter != '') filter += ' AND ';
        filter += 'contains (#message, :val2)';
    }

    if(start != '' && end == '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: start + ' 00:00:00'};
        if(filter != '') filter += ' AND ';
        filter += '#created >= :val3';
    }

    if(start == '' && end != '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: end + ' 23:59:59'};
        if(filter != '') filter += ' AND ';
        filter += '#created <= :val3';
    }


    if(start != '' && end != '') {
        attrNames['#created'] = 'created';
        attrValues[':val3'] = {S: start + ' 00:00:00'};
        attrValues[':val4'] = {S: end + ' 23:59:59'};
        if(filter != '') filter += ' AND ';
        filter += '#created BETWEEN :val3 AND :val4';
    }

    if(filter != '') {
        params['ExpressionAttributeNames'] = attrNames;
        params['ExpressionAttributeValues'] = attrValues;
        params['FilterExpression'] = filter;
    }

    dynamodb.scan(params, function(err, data) {
        if (err) {
            console.log(err, err.stack); // an error occurred
        } else {
            console.log(data);           // successful response
            $('#result').empty();
            items = data.Items;
            for (var i = items.length - 1; i >= 0; i--) {
                var item = items[i]
                $('#result').append('<div>' + item.channel.S + '</div>');
                $('#result').append('<div>' + item.created.S + '</div>');
                $('#result').append('<div>' + item.id.S + '</div>');
                $('#result').append('<div>' + item.message.S + '</div>');
                $('#result').append('<br />');
            };
        }
    });

}
```

# 課題
- 発言が**"編集"**された場合に、**検知不可**
- 発言が**”削除”**された場合も、**検知不可**
    - 新規発言はbotが拾ってくれるのですが、上記は拾ってくれないようです
- Public, Private 一即多に収集
    - 発言を拾うには、channelにbotを招待しなくてはいけません
    - Private channelにbotを招待した結果、それを誰もが検索できていいのか？あまりよろしくないと思います
    - 誰がどのchannelに参加していて、検索権限があるか、というところまで考えるとだいぶ大変です。。
    - 結局、そんなクリティカルな話しをしているわけではないので、誰でも検索可能なchannelのみbotを招待するようにしています


# 最大の課題
- **DMが検知不可**
    - DMは1対1のやり取りなので、botを招待することができません
    - やるとしたら、botを含んだ3人のPrivateChannelを作成する必要がありますが、面倒臭すぎる・・

DMは別に検索できなくても良くない？と、言われたので、とりあえず諦めました。。

# でも・・
DMの利用率だいぶ高いです＼(^o^)／（うちのチームでは平均70％がDM）

# 結局
きちんとログを検索したければ、有料会員になったほうがいいです
もしもっといい方法があれば教えて下さいε≡≡ﾍ( ´Д`)ﾉ