Slackでよく使われる単語をまとめてみた

こんにちは、フィッシュです🐟業務や趣味でGCPを触っている釣り好きエンジニアです。GCPと釣りの融合で [Cloud AutoML Visionを使ってカレイの種類を識別](https://medium.com/google-cloud-jp/cloud-automl-vision%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%AB%E3%83%AC%E3%82%A4%E3%81%AE%E7%A8%AE%E9%A1%9E%E3%82%92%E8%AD%98%E5%88%A5%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F-5a1cb2379e29) とかやってます。最近Goを触りはじめたので表題のスクリプトをGoで書いてみました。

## やったこと
- Slack APIからチャンネルの会話ログを取得する
- 会話のログを品詞分解し単語を取り出す
- 同じ単語の数を数えて配列を作る
- その配列を並び替えてログ出力する

## Slack APIからチャンネルの会話ログを取得する
まずは[こちら](https://api.slack.com/methods/channels.history/test)からチャンネルの会話ログを取得してみましょう。

### 取得方法
- ワークスペースを選択する
- 取得したいチャンネルを選択する 例 #general
- countを1000にする
- 上記の入力が終えたら Test Methodをクリックする
- リクエストURLとレスポンスが表示されます
- リクエストURLをどこかに控えておきます

<img width="566" alt="Screen Shot 2018-12-08 at 11.19.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/6d667f9b-9c6c-4cca-80ef-a81cf571460e.png">

### Goで上記のURLを叩く

```go:main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type Message struct {
	Text string `json:"text"`
}

type Channel struct {
	Messages []Message `json:"messages"`
}

func main() {
	// 控えたURLを入れる
	url := "https://slack.com/api/channels.history?token="
	res, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	if res.StatusCode != 200 {
		return
	}

	defer res.Body.Close()
	var c Channel
	decoder := json.NewDecoder(res.Body)
	err = decoder.Decode(&c)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(c)
}
```

## 会話のログを品詞分解し単語を取り出す
上記で取得したログは文章になっているので、そこから単語だけを抽出します。品詞分解には mecabを使います。Go向けにラッピングされたソースがあるのでこちらを使います。ありがたやー
https://github.com/bluele/mecab-golang

### mecabを準備

```
$ brew insatll mecab mecab-ipadic

$ which mecab-config
/usr/local/bin/mecab-config　//こんな感じになればOK

// 環境変数を作成
$ export CGO_LDFLAGS="`mecab-config --libs`"
$ export CGO_CFLAGS="-I`mecab-config --inc-dir`"
```

### Goでmecabを使って品詞分解
APIでテキストを取得し、その中から３文字以上の名詞だけを取り出します。

```go:main.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"unicode/utf8"

	"github.com/bluele/mecab-golang"
)

type Message struct {
	Text string `json:"text"`
}

type Channel struct {
	Messages []Message `json:"messages"`
}

func main() {
...中略

	m, err := mecab.New("-Owakati")
	if err != nil {
		fmt.Printf("Mecab instance error. err: %v", err)
	}
	defer m.Destroy()
	decoder := json.NewDecoder(res.Body)
	err = decoder.Decode(&c)
	if err != nil {
		log.Fatal(err)
	}

	var tmp []string
	for _, msg := range c.Messages {
		parseToNode(m, msg.Text, &tmp)
	}

	fmt.Println(tmp)
}

func parseToNode(m *mecab.MeCab, s string, t *[]string) {
	tg, err := m.NewTagger()
	if err != nil {
		panic(err)
	}
	defer tg.Destroy()
	lt, err := m.NewLattice(s)
	if err != nil {
		panic(err)
	}
	defer lt.Destroy()

	node := tg.ParseToNode(lt)
	for {
		features := strings.Split(node.Feature(), ",")
		if utf8.RuneCountInString(features[6]) > 2 {
			if features[0] == "名詞" {
				*t = append(*t, features[6])
			}
		}
		if node.Next() != nil {
			break
		}
	}
}
```

こんな感じのログが出力される

```
$ [こちら 結婚式 土曜日 日曜日 結婚式 チャンネル]
```

## 同じ単語の数を数えて配列を作る
このままだと普通の文字配列なので同じ単語をカウント付きでまとめます

```go:main.go
...中略
type Buzzword struct {
	Word  string
	Count int
}

func main() {
...中略
	var buzzwords []Buzzword
	for word, count := range wordCount(tmp) {
		buzzwords = append(buzzwords, Buzzword{word, count})
	}
	fmt.Println(buzzwords)
}

func wordCount(a []string) map[string]int {
	c := make(map[string]int)
	for _, word := range a {
		c[word]++
	}
	return c
}
```

結果例

```
$ [{お答え 1} {大丈夫 10} {どちら 2} {どっち 1} {タイミング 3} {マグロ 2}]
```

どの単語がいくつ入っているところまで分かりました！

## 並び替えをして完成
並び替えはこんな感じで実装しました

```go:main.go
package main

import (
	"log"
	"sort"
)

type Buzzword struct {
	Word  string
	Count int
}

type Buzzwords []Buzzword

func (p Buzzwords) Len() int {
	return len(p)
}

func (p Buzzwords) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

type ByCount struct {
	Buzzwords
}

func (b ByCount) Less(i, j int) bool {
	return b.Buzzwords[i].Count > b.Buzzwords[j].Count
}

func main() {
	var people Buzzwords = []Buzzword{
		{"お答え", 1},
		{"大丈夫", 10},
		{"どちら", 2},
		{"どっち", 1},
		{"タイミング", 3},
		{"マグロ", 2},
	}

	sort.Sort(ByCount{people})
	log.Println(people)
}
```

多い順に並び替わります

```
$ [{大丈夫 10} {タイミング 3} {どちら 2} {マグロ 2} {お答え 1} {どっち 1}]
```

よくうろうろしているSlackの結果はこんな感じ

```
チャンネル => 92
お願い => 58
イベント => 57
こちら => 24
オンライン => 22
申し込み => 16
みたい => 16
ページ => 14
スピーカー => 13
大丈夫 => 10
コード => 9
みんな => 9
レポート => 7
カレー => 7
フォーム => 7
スライド => 7
セッション => 7
みなさん => 7
トピック => 6
```

## 参考になったリンク
- [How to count number of repeating words in a given String?
](http://www.golangprograms.com/how-to-count-number-of-repeating-words-in-a-given-string.html)
- [Golang の sort パッケージ](https://qiita.com/Jxck_/items/fb829b818aac5b5f54f7)

## 全部入りのソースコード
上記のソースコードを突っ込んだ main.go をGitHubにあげました。とくにリファクタリングなどしていないので、変なところがあったらIssueとかプルリクエストを貰えると嬉しいです:)

https://github.com/flatfisher/fish-go/blob/master/slack-buzzword/main.go