はじめに

githubにあげてるんでめんどくさい方はこちらからソースを見てください。画像を多めに使用しているため見にくいかとは思いますがご了承ください。本記事はGoogleカスタム検索エンジンを用いて画像の取得部分の実装をしています。

今回のゴール


botに対してメンション+検索したいものを送った際に画像URLを返してくれるbotを作ります。
例:@botの名前 検索したいもの

環境

go 1.10.0

実装の前に

1. Slack Botの設定

こちらからbotの作成をしてください。赤く塗りつぶされている箇所がTokenになります。このTokenは後にチャンネルの情報取得のため後に使用します。

2. Googleカスタム検索エンジンの設定

2.1 プロジェクトの作成

Googleアカウントにログイン後、こちらからプロジェクトの作成を行ってください。

2.2 Custom Search APIの有効化

こちらに移動し検索欄にcustom searchと入力Custom Search APIを選択してください。

選択すると下記のような画面になると思うためAPI有効にしてください。(プロジェクトの作成を求められた場合は指示に従って作成してください。)

2.2 APIキーの作成

こちらから認証情報の作成し、APIキーの作成を行ってください。APIキーは後に使います。

2.3 カスタム検索エンジンの追加

こちらからカスタム検索エンジンの追加を行います。
検索エンジンの欄にwww.google.comと入力し、言語を設定してください。(僕は日本語にしました)

2.4 カスタム検索エンジンの設定・ID取得

カスタム検索エンジンの追加完了後、コントロールパネルに移動し画像検索をオンにしてください。また、検索するサイトをウェブ全体に設定してください。これでカスタム検索エンジンの設定は完了です。詳細から検索エンジンIDを取得してください。検索エンジンIDは後に使います。

実装

1. 画像検索

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

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 {
        Kind string `json:"kind"`
    URL struct {
        Type     string `json:"type"`
        Template string `json:"template"`
    } `json:"url"`
    Queries struct {
        Request []struct {
            Title          string `json:"title"`
            TotalResults   string `json:"totalResults"`
            SearchTerms    string `json:"searchTerms"`
            Count          int    `json:"count"`
            StartIndex     int    `json:"startIndex"`
            InputEncoding  string `json:"inputEncoding"`
            OutputEncoding string `json:"outputEncoding"`
            Safe           string `json:"safe"`
            Cx             string `json:"cx"`
            SearchType     string `json:"searchType"`
        } `json:"request"`
        NextPage []struct {
            Title          string `json:"title"`
            TotalResults   string `json:"totalResults"`
            SearchTerms    string `json:"searchTerms"`
            Count          int    `json:"count"`
            StartIndex     int    `json:"startIndex"`
            InputEncoding  string `json:"inputEncoding"`
            OutputEncoding string `json:"outputEncoding"`
            Safe           string `json:"safe"`
            Cx             string `json:"cx"`
            SearchType     string `json:"searchType"`
        } `json:"nextPage"`
    } `json:"queries"`
    Context struct {
        Title string `json:"title"`
    } `json:"context"`
    SearchInformation struct {
        SearchTime            float64 `json:"searchTime"`
        FormattedSearchTime   string  `json:"formattedSearchTime"`
        TotalResults          string  `json:"totalResults"`
        FormattedTotalResults string  `json:"formattedTotalResults"`
    } `json:"searchInformation"`
    Items []struct {
        Kind        string `json:"kind"`
        Title       string `json:"title"`
        HTMLTitle   string `json:"htmlTitle"`
        Link        string `json:"link"`
        DisplayLink string `json:"displayLink"`
        Snippet     string `json:"snippet"`
        HTMLSnippet string `json:"htmlSnippet"`
        Mime        string `json:"mime"`
        Image struct {
            ContextLink     string `json:"contextLink"`
            Height          int    `json:"height"`
            Width           int    `json:"width"`
            ByteSize        int    `json:"byteSize"`
            ThumbnailLink   string `json:"thumbnailLink"`
            ThumbnailHeight int    `json:"thumbnailHeight"`
            ThumbnailWidth  int    `json:"thumbnailWidth"`
        } `json:"image"`
    } `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というツールを使って生成しました。JSONオブジェクトを入れると構造体をいい感じで生成してくれるやつです。

2. slackのコメント監視、画像URLの送信

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

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パッケージを用いて実装しています。今回はメッセージ機能を用いて画像のURLを送りたいという事でcase *slack.MessageEventを見てください。こちらではbotに対してメンションでメッセージを送信された際に先ほど作成したSearchImageメソッドを使い画像URLを取得しslack上に送信する感じになっています。起動するまではユーザーIDがわからないです。

3. とりあえず起動

最後にmain.goを作成します。

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
りんごの前に赤枠で囲ってある<@U9U7Z06CV>がbotのユーザーIDになります。slack.gobotのユーザーIDと書いてる箇所を自分のbotのユーザーIDに変更しましょう。

5. 完成

botに対してメンションを送って画像のURLが返ってくれば完成です。
今回作成したbotを各チャンネルで使いたい場合は、使いたいチャンネルに追加して行って下さい。

まとめ

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

参考にさせていただいたサイト

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.