キーワードを投げかけるとwikiで調べてくれるLineBot

# キーワードを投げかけるとwikiで調べてくれるLineBot

[前回](https://qiita.com/ken1ro/items/2c4cd04bc5433ccbc217)作ったオウム返しするLineBotを拡張して、話しかけるとWikiで調べて回答してくれるLineBotを作ってみた。

## 大まかな流れ

* wikiからキーワード検索してくれるAPIを使う
* このAPIにlineから受け取った言葉を渡す関数を作る
* APIから受け取った結果をJSONパースして、Lineに返す

一番下に完成イメージがあります。

## WikiへのアクセスAPI

wikiからキーワード検索にあたり、[こちら](http://wikipedia.simpleapi.net/)のAPIを利用させていただきました。

>ユーザーローカル「Wikipedia API」とは？
　キーワードを指定すると、その言葉に関するwikipediaの記述をダイジェストとして返します。コンテンツ内に百科事典機能を持つサイトを簡単に作ることができます。

wikiAPIの使い方は、以下を呼び出すことで、keywordでの前方一致でwikiを検索した結果がJSON形式で得られます。

```
http://wikipedia.simpleapi.net/api?keyword=keyword&output=json
```

戻ってきたJSONは以下の内容を含むようです。

```
language : 言語名
id : Wikipedia内の管理コード
url : Wikipediaにリンクする際のURL
title : キーワードのタイトル
body : 本文のダイジェスト
length : 本文の長さ
redirect : 別キーワードへのリダイレクトがあるかどうか。あれば1、なければ0。
strict : そのキーワードと完全一致する場合には1。完全一致しない場合は0。
datetime : 更新日付
```

このAPIでは、xml,rss,json,html,javascript,php,tsvで結果を得られるようですが、形式により、上記のものと若干の違いがありそうです。

## 修正箇所１：WikiAPIを呼び出すハンドラを作成

単純に、lineから受け取ったキーワードをwikiAPIへ投げる、戻ってきたjsonをパースするための関数をpkg`wiki`として追加しました。

```go:wikihandler.go
package wiki

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/comail/colog"
)

// JSONパース用の構造体を定義
type WikiInformation struct {
	Language  string `json:"language"`
	Wikiid    string `json:"id"`
	Wikiurl   string `json:"url"`
	Wikititle string `json:"title"`
	Wikibody  string `json:"body"`
}

// APIの結果は複数帰ってくるのでそれらを受けるため、配列で定義
type Wikiinfos []WikiInformation

// 引数をキーワードに取り、APIを投げてJSONパース後の構造体を返り値にとる関数
// main側でこの関数にlineから受け取った言葉をkeywordとして投げつけて、結果を待つ。
func WikiAPIGet(keyword string) Wikiinfos {

	colog.SetDefaultLevel(colog.LDebug)
	colog.SetMinLevel(colog.LTrace)
	colog.SetFormatter(&colog.StdFormatter{
		Colors: true,
		Flag:   log.Ldate | log.Ltime | log.Lshortfile,
	})
	colog.Register()

	// log.Printf("info: wikiapiget")

	url := make([]byte, 0, 10)
	url = append(url, "http://wikipedia.simpleapi.net/api?keyword="...)
	url = append(url, keyword...)
	url = append(url, "&output=json"...)

	res, err := http.Get(string(url))
	if err != nil {
		log.Printf("warn: Get res nil")
		log.Fatal(err)
	}

	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("warn: res.Body.readall")
		log.Fatal(err)
	}

	var wikiinfos Wikiinfos
	err = json.Unmarshal(body, &wikiinfos)
	if err != nil {
		log.Printf("warn: wikiinfos json parse")
		log.Fatal(err)
	}

	return wikiinfos
}
```

このファイルを書いて、`github.com/line/line-bot-sdk-go/linebot/wiki`に保存。

## 修正箇所２：server.goに上記関数を呼び出す部分を追加

[前回](https://qiita.com/ken1ro/items/2c4cd04bc5433ccbc217)作ったBotのmainである、`server.go`に関数呼び出し部分を追加する。

### wiki packageをimport

作ったpkgを呼び出すため、importを追加。

```go:server.go
import (
	（略）
  "github.com/line/line-bot-sdk-go/linebot/wiki"
)
```

### 関数呼び出し部

ざっくりやっていることは次の通り。

* APIにlineから受け取った`message.Text`をキーワードとして渡す。
* 戻りを`Wikiinfos`型で受け取る。（これは`[]Wikiinformation`型）
* APIの戻り値のエラーハンドリング
  * WikiAPIは前方一致で検索し、一致する結果がない場合は、nilを返す様子
  * nilを含んだBodyをJSON パースすると、空の配列が返される
  * ということが実験で確認できたので、`wikiinfos`の長さが0の場合をエラーとした
  * エラーの場合は、「何？それ美味しいの？」的なメッセージを返す
* エラーでない場合は、戻り値から`title`,`body`,`url`を取り出してLineへPOST
  * 本当は各要素を改行で繋ぎたかったがうまいやり方が見つからず・・・

という内容を以下で実施しています。
入れる場所は、前回のソースと見比べていただければ。

```go:server.go
for _, event := range events {
	if event.Type == linebot.EventTypeMessage {
		switch message := event.Message.(type) {
		case *linebot.TextMessage:
			log.Printf("info: %s %s ", event.ReplyToken, message.Text)

			var wikiinfos wiki.Wikiinfos
			wikiinfos = wiki.WikiAPIGet(message.Text)
			// for debug :log.Printf("info: %s ", wikiinfos[0:])
			if len(wikiinfos) == 0 {
				//wikiAPIの結果がエラーならなにそれ美味しいの？的なメッセージを返す
				comment := make([]byte, 0, 10)
				comment = append(comment, "「"...)
				comment = append(comment, message.Text...)
				comment = append(comment, "」？って何？それ美味しいの？"...)
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(string(comment))).Do(); err != nil {
					log.Print(err)
				}
			} else {
				comment := make([]byte, 0, 10)
				comment = append(comment, "「"...)
				comment = append(comment, wikiinfos[0].Wikititle...)
				comment = append(comment, "」なら知ってるよ！！　"...)
				comment = append(comment, wikiinfos[0].Wikibody...)
				comment = append(comment, "　　　"...)
				comment = append(comment, wikiinfos[0].Wikiurl...)
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(string(comment))).Do(); err != nil {
					log.Print(err)
				}
			}
			/* もともとオウム返しだった部分
			if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(message.Text)).Do(); err != nil {
				log.Print(err)
			}
			*/
		}
```

で、こいつを前回と同様にEC2へアップして起動すると・・・

## 完成イメージ！！

Lineで話しかけるとこんな感じになります。

![けん_ver_1_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409199/2b9fe54a-97bd-6559-7540-d04cabe17447.png)

このまま放置したいけど、課金が気になるな〜。。。
lambdaにしようかな。

以上、golang入門編として一泊二日の合宿でできた成果でした〜。
書き方がよろしくないところがあると思いますが、なんとか実現できた、という感じです。
ただ、golang入門というよりは、lineAPI,APIGW,などなどAPI入門、という感じでした。。