キーワードを投げかけるとwikiで調べてくれるLineBot
前回作ったオウム返しするLineBotを拡張して、話しかけるとWikiで調べて回答してくれるLineBotを作ってみた。
大まかな流れ
- wikiからキーワード検索してくれるAPIを使う
- このAPIにlineから受け取った言葉を渡す関数を作る
- APIから受け取った結果をJSONパースして、Lineに返す
一番下に完成イメージがあります。
WikiへのアクセスAPI
wikiからキーワード検索にあたり、こちらの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で結果を得られるようですが、形式により、上記のものと若干の違いがありそうです。
修正箇所1:WikiAPIを呼び出すハンドラを作成
単純に、lineから受け取ったキーワードをwikiAPIへ投げる、戻ってきたjsonをパースするための関数をpkgwiki
として追加しました。
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
に保存。
修正箇所2:server.goに上記関数を呼び出す部分を追加
前回作ったBotのmainである、server.go
に関数呼び出し部分を追加する。
wiki packageをimport
作ったpkgを呼び出すため、importを追加。
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- 本当は各要素を改行で繋ぎたかったがうまいやり方が見つからず・・・
という内容を以下で実施しています。
入れる場所は、前回のソースと見比べていただければ。
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で話しかけるとこんな感じになります。
このまま放置したいけど、課金が気になるな〜。。。
lambdaにしようかな。
以上、golang入門編として一泊二日の合宿でできた成果でした〜。
書き方がよろしくないところがあると思いますが、なんとか実現できた、という感じです。
ただ、golang入門というよりは、lineAPI,APIGW,などなどAPI入門、という感じでした。。