【Slack】画像を検索してくれるbotを作る

## はじめに
githubにあげてるんでめんどくさい方は[こちら](https://github.com/JinOketani/slack_image_bot)からソースを見てください。画像を多めに使用しているため見にくいかとは思いますがご了承ください。本記事は[Googleカスタム検索エンジン](https://developers.google.com/custom-search/)を用いて画像の取得部分の実装をしています。

## 今回のゴール
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/9580fe00-7f06-e7cc-d268-5706f033b750.gif" height="300" width="500" style="text-align:center">
botに対してメンション+検索したいものを送った際に画像URLを返してくれるbotを作ります。
例：`@botの名前 検索したいもの`

## 環境
go 1.10.0

## 実装の前に
### 1. Slack Botの設定
[こちら](https://my.slack.com/services/new/bot)からbotの作成をしてください。赤く塗りつぶされている箇所がTokenになります。このTokenは後にチャンネルの情報取得のため後に使用します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/10c2e30e-1333-b351-b05c-026ad831bbc5.png" height="441" width="500">

### 2. Googleカスタム検索エンジンの設定
#### 2.1 プロジェクトの作成
Googleアカウントにログイン後、[こちら](https://console.cloud.google.com/cloud-resource-manager)からプロジェクトの作成を行ってください。

#### 2.2 Custom Search APIの有効化
[こちら](https://console.cloud.google.com/apis/library)に移動し検索欄に`custom search`と入力Custom Search APIを選択してください。
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/33248b66-e1e1-45cd-d3f0-d89ce75b71fb.png" height="262" width="500">
選択すると下記のような画面になると思うためAPI有効にしてください。（プロジェクトの作成を求められた場合は指示に従って作成してください。）
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/c576769e-f7e0-81d9-2f53-46c2c40c2142.png" height="352" width="500">

#### 2.2 APIキーの作成
[こちら](https://console.cloud.google.com/apis/credentials)から認証情報の作成し、APIキーの作成を行ってください。APIキーは後に使います。
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/9f6baeae-3b58-2243-c1ee-8e07bee40ba8.png" height="334" width="500">

#### 2.3 カスタム検索エンジンの追加
[こちら](https://cse.google.com/cse/all)からカスタム検索エンジンの追加を行います。
検索エンジンの欄に`www.google.com`と入力し、言語を設定してください。（僕は日本語にしました）
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/cefee8fc-e9d2-ae7b-6176-9a2a3f28c7f8.png" height="290" width="300">

#### 2.4 カスタム検索エンジンの設定・ID取得
カスタム検索エンジンの追加完了後、コントロールパネルに移動し**画像検索をオン**にしてください。また、検索するサイトをウェブ全体に設定してください。これでカスタム検索エンジンの設定は完了です。詳細から検索エンジンIDを取得してください。検索エンジンIDは後に使います。
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/5ab62bb3-4029-dd00-0f79-a3529eb51f5c.png" height="268" width="500">

## 実装
### 1. 画像検索
ベースとなるURLです。
`https://www.googleapis.com/customsearch/v1?key=APIキー&cx=検索エンジンID&searchType=image&num=1&q=検索したい文字列`
今回は画像のみの取得ということでserchTypeを画像のみ（`searchType=image`）としています。また、画像の取得件数を１件だけ（`num=1`）にしてあります。先ほど取得したAPIキーと検索エンジンIDを入れ、検索したい文字列を入れてみてください。JSON形式で画像URLが１件取得できると思います。リクエストパラメーターを詳しく見たいという方は[こちら](https://developers.google.com/custom-search/json-api/v1/reference/cse/list)からドキュメントを参照してください。
それでは、`botディレクトリ`を作成し画像を検索する`image.go`を作成し、以下のように書いていきましょう。

```golang:bot/image.go

package main

import (
	"log"
	"io/ioutil"
	"encoding/json"
	"fmt"
	"strings"
	"net/http"
	"os"
)

type Search struct {
	Key      string
	EngineId string
	Type     string
	Count    string
}

type Result struct {
	Items []struct {
		Link        string `json:"link"`
	} `json:"items"`
}

func SearchImage(word string) string {
	baseUrl := "https://www.googleapis.com/customsearch/v1"
	s := Search{os.Getenv("CUSTOM_SEARCH_KEY"), os.Getenv("CUSTOM_SEARCH_ENGINE_ID"), "image", "1"}
	word = strings.TrimSpace(word)

	url := baseUrl + "?key=" + s.Key + "&cx=" + s.EngineId + "&searchType=" + s.Type + "&num=" + s.Count + "&q=" + word
	log.Println(url)

	imageUrl := ParseJson(url)
	return imageUrl
}

func ParseJson(url string) string {
	var imageUrl = "not search image"

	response, err := http.Get(url)
	if err != nil {
		log.Println("get error:", err)
	}

	defer response.Body.Close()

	byteArray, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
	}

	jsonBytes := ([]byte)(byteArray)
	data := new(Result)
	if err := json.Unmarshal(jsonBytes, data); err != nil {
		fmt.Println("json error:", err)
	}
	if data.Items != nil {
		imageUrl = data.Items[0].Link
		log.Println(imageUrl)
	}

	return imageUrl
}
```

`ParseJson`メソッドはurlを受け取りjsonをUnmarshalでparseし、画像URLを返しています。`SearchImage`メソッドでは検索したい文字列を受け取りURLを生成しParseJsonに渡して画像URLを受け取る感じです。Resultの構造体...のなった方はいると思いますが、[JSON-to-GO](https://mholt.github.io/json-to-go/)というツールを使って生成しました。JSONオブジェクトを入れると構造体をいい感じで生成してくれるやつです。

### 2. slackのコメント監視、画像URLの送信
次にslackへ画像URLを送信やメンションの受け取りなどの行う`slack.go`を以下のように書いていきましょう。

```golang:bot/slack.go
package main

import (
	"log"
	"regexp"
	"github.com/nlopes/slack"
	"strings"
)

func Run(api *slack.Client) int {
	var image string

	rtm := api.NewRTM()
	go rtm.ManageConnection()

	for {
		select {
		case msg := <-rtm.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.HelloEvent:
				log.Println("====Start====")
			case *slack.MessageEvent:
				fmt.Printf("Message: %v\n", ev)
				if strings.Contains(ev.Text, "botのユーザーID") { // ex: <@U9U7Z06CV>
					rep := regexp.MustCompile(`botのユーザーID`) // ex: <@U9U7Z06CV>
					image = rep.ReplaceAllString(ev.Text, "")
					image = SearchImage(image)
					rtm.SendMessage(rtm.NewOutgoingMessage(image, ev.Channel))
				}
			case *slack.InvalidAuthEvent:
				log.Println("Invalid credentials")
				return 1
			}
		}
	}
}
```
slackの情報を取得は[nlopes/slack](https://github.com/nlopes/slack)パッケージを用いて実装しています。今回はメッセージ機能を用いて画像のURLを送りたいという事で`case *slack.MessageEvent`を見てください。こちらではbotに対してメンションでメッセージを送信された際に先ほど作成した`SearchImage`メソッドを使い画像URLを取得しslack上に送信する感じになっています。**起動するまではユーザーIDがわからないです。**

### 3. とりあえず起動
最後にmain.goを作成します。

```golang:main.go
package main

import (
	"github.com/nlopes/slack"
	"os"
)

func main() {
	token := slack.New("slackのAPIトークン")
	os.Exit(Run(token))
}
```
maing.goを作成後、`go run main.go`で起動してみてください。`====Start====`と表示されれば起動成功です。

### 4. ユーザーIDの設定
起動後、botに対してメンションを送ってください。今回はbotに対してメンションで「りんご」と送ってみたいと思います。
その際にログで以下のようなものが表示されると思います。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f38313334312f66323061633062302d356537622d303235332d336439372d3934393434396162386135642e706e67.png](https://qiita-image-store.s3.amazonaws.com/0/81341/2a9ca992-b535-6ad4-c349-4b3ceeaf5c3b.png)
りんごの前に赤枠で囲ってある`<@U9U7Z06CV>`がbotのユーザーIDになります。`slack.go`の`botのユーザーID`と書いてる箇所を自分のbotのユーザーIDに変更しましょう。

### 5. 完成
botに対してメンションを送って画像のURLが返ってくれば完成です。
今回作成したbotを各チャンネルで使いたい場合は、使いたいチャンネルに追加して行って下さい。
<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/9580fe00-7f06-e7cc-d268-5706f033b750.gif" height="300" width="500" style="text-align:center">

## まとめ
無事に画像が取得できましたか？何かあれば[こちら](https://github.com/JinOketani/slack_image_bot)にもソースコードをあげてあるのでぜひ見て下さい。
golangで初めてbotを開発して見ましたがJsonのparse処理など慣れない部分は多々ありました。現状slackでは.svgの画像を表示することができないため.png, .jpgの画像をのみ取得する必要があるかもしれません。今後
、and検索or検索にも対応したいです！

## 参考にさせていただいたサイト
- https://kuroeveryday.blogspot.jp/2017/10/tutorial-slack-rtm-api-with-golang.html
- http://ryutamaki.hatenablog.com/entry/2014/01/18/171640
- http://tikasan.hatenablog.com/entry/2017/04/26/110854