# Alexa x SpreadSheet連携 ###### tags: `研究` ## イベント通知 ### 概要 スプレッドシート上に入力されたイベント情報を,Alexaで通知します。とりあえずなのでソースコードが雑です。 ![](https://i.imgur.com/Ur1KsHY.png) ### 実装 このデータは,APIで以下のように取得できます(Google Apps Scriptで実装)。 ```json { "time": "2020-12-07T02:08:21.033Z", "events": [ { "id": 1, "title": "餅つき大会", "content": "餅ついてみんなで食べよう", "status": 1, "time_from": "2021-01-01T01:00:00.000Z", "time_to": "2021-01-01T05:00:00.000Z", "type1": 4, "type2": 16, "type3": 2, "target": 0, "location_lat": 35.2113666, "location_lon": 136.8995995, "place": "北区役所", "contact": "大前" }, { "id": 2, "title": "餅つき大会", "content": "餅ついてみんなで食べよう", "status": 1, "time_from": "2021-01-01T01:00:00.000Z", "time_to": "2021-01-01T05:00:00.000Z", "type1": 4, "type2": 16, "type3": 2, "target": 0, "location_lat": 35.2113666, "location_lon": 136.8995995, "place": "北区役所", "contact": "大前" } ] } ``` このJSONを返すためのGASのソースはこんな感じです。 ```javascript= function getData(sheetName) { var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName); var rows = sheet.getDataRange().getValues(); var keys = rows.splice(0, 1)[0]; return rows.map(function(row) { var obj = {}; row.map(function(item, index) { if (String(keys[index]) == "id"){ obj[String(keys[index])] = Number(item); } else if (String(keys[index]) == "status"){ obj[String(keys[index])] = Number(item); } else if (String(keys[index]) == "time_from"){ time_from = new Date(item) obj[String(keys[index])] = time_from; } else if (String(keys[index]) == "time_to"){ time_to = new Date(item) obj[String(keys[index])] = time_to; } else if (String(keys[index]) == "type1" || String(keys[index]) == "type2" || String(keys[index]) == "type3"){ obj[String(keys[index])] = Number(item); } else if (String(keys[index]) == "target"){ obj[String(keys[index])] = Number(item); } else if (String(keys[index]) == "location_lat" || String(keys[index]) == "location_lon"){ obj[String(keys[index])] = Number(item); } else{ obj[String(keys[index])] = String(item); } }); return obj; }); } function doGet() { var data = getData('events'); var date = new Date(); return ContentService.createTextOutput(JSON.stringify({"time": date, "events": data}, null, 2)) .setMimeType(ContentService.MimeType.JSON); } ``` このデータをalexaのwebホストでパースします。 * サーバーはmdgの実験用サーバーを使用 * Go言語による実装 * イベント情報の構造体は次の通り ```go type EventData struct { Time time.Time `json:"time"` Events []struct { ID int `json:"id"` Title string `json:"title"` Content string `json:"content"` Status int `json:"status"` TimeFrom time.Time `json:"time_from"` TimeTo time.Time `json:"time_to"` Type1 int `json:"type1"` Type2 int `json:"type2"` Type3 int `json:"type3"` Target int `json:"target"` LocationLat float64 `json:"location_lat"` LocationLon float64 `json:"location_lon"` Place string `json:"place"` Contact string `json:"contact"` } `json:"events"` } ``` 全てのソースは次の通り。ひとまず,一番上のイベントが表示されるようになっている。 ```go= package main import ( "encoding/json" "fmt" alexa "github.com/mikeflynn/go-alexa/skillserver" "io/ioutil" "net/http" "os" "time" ) type EventData struct { Time time.Time `json:"time"` Events []struct { ID int `json:"id"` Title string `json:"title"` Content string `json:"content"` Status int `json:"status"` TimeFrom time.Time `json:"time_from"` TimeTo time.Time `json:"time_to"` Type1 int `json:"type1"` Type2 int `json:"type2"` Type3 int `json:"type3"` Target int `json:"target"` LocationLat float64 `json:"location_lat"` LocationLon float64 `json:"location_lon"` Place string `json:"place"` Contact string `json:"contact"` } `json:"events"` } var applications = map[string]interface{}{ "/echo/event-notify": alexa.EchoApplication{ // Route AppID: os.Getenv("NOTIFY_APP_ID"), // Dockerに環境変数として定義しておく Handler: echoIntentHandler, }, } var typeList = []string{"学び・講座", "体感・体験", "遊び", "コミュニティ・交流", "音楽", "文化・芸術", "スポーツ", "子ども・子育て", "健康・福祉", "環境・自然", "防災・防犯", "生き物", "ボランティア", "観光", "祭り", "食", "買い物", "生活", "仕事・ビジネス"} var targetList = []string{"誰でも", "乳児", "幼児", "小学生", "中学生", "高校生", "大学生", "社会人", "保護者", "高齢者", "妊産婦", "障がい者", "外国人"} func main() { alexa.Run(applications, "3000") } func echoIntentHandler(w http.ResponseWriter, r *http.Request) { echoReq := alexa.GetEchoRequest(r) if echoReq.GetRequestType() == "LaunchRequest" { events := unmarshal() content := events.Events[0].Content time_from := events.Events[0].TimeFrom.Format("2006/1/2 3:04 pm") time_to := events.Events[0].TimeTo.Format("2006/1/2 3:04 pm") place := events.Events[0].Place etype := typeList[events.Events[0].Type1] + ", " + typeList[events.Events[0].Type2] + ", " + typeList[events.Events[0].Type3] target := targetList[events.Events[0].Target] url := "https://www.cocoyoko.net/common/images/cate_title_event.jpg" information := "概要: " + content + "\n場所: " + place + "\n日時: " + time_from + "〜" + time_to + "\nカテゴリ: " + etype + "\n対象者: " + target echoResp := alexa.NewEchoResponse().OutputSpeech("こんにちは!このようなイベントが見つかりました。").StandardCard(events.Events[0].Title, information, url, url).EndSession(false) json, _ := echoResp.String() w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Write(json) } else if echoReq.GetRequestType() == "IntentRequest" { var echoResp *alexa.EchoResponse switch echoReq.GetIntentName() { case "HelloWorldIntent": echoResp = alexa.NewEchoResponse().OutputSpeech("こんにちは!ご挨拶嬉しいです。").EndSession(true) default: echoResp = alexa.NewEchoResponse().OutputSpeech("I'm sorry, I didn't get that. Can you say that again?").EndSession(false) } json, _ := echoResp.String() w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Write(json) } } func unmarshal() *EventData { url := os.Getenv("SpreadSheetAPI") data := new(EventData) resp, _ := http.Get(url) defer resp.Body.Close() byteArray, _ := ioutil.ReadAll(resp.Body) if err := json.Unmarshal(byteArray, data); err != nil { fmt.Println("JSON Unmarshal error:", err) } return data } ``` ### 問題点・検討課題 * スキルを呼び出すたびに,SpreadSheetを読みにいっている(技術) * レスポンスが割と長い(5秒くらい)→イベントの数が増えると...?Alexaの待ち時間をオーバーする可能性がある。 * では,ある程度データをmdgサーバーに保持する?→それならばスプレッドシートを使わずに初めからデータベースに登録してもらう方法で良い→簡易なwebアプリを実装してwebページでもイベントを閲覧可能にするのはどうか * そもそもなぜスプレッドシート? * 扱いやすさが理由なら,わかりやすいUIのwebアプリで代替え可能 * スプレッドシートで普段からイベントを管理しているなら,csvをアップロードしてもらってそれをDBに保存する方法が考えられそう * どういう方法でイベントを通知したいのか?(ニーズ) * 高齢者にAlexa上で選んでもらう?(市の広報誌ではなぜダメなのか?,==市の広報誌と何が違うのか==) * LINEでプッシュ通知するのは良いかも(町内会公式アカウントの機能の1つとしても?) * ひとまず,==どのような情報をどのように伝えたいかを知る必要がある==(それからでないと設計が難しい)。その上で, * 会話フローの決定(最初に何を選んで,次に何を選んで...) * 画面UIの決定(必要な情報は何か?) ### メモ * できる限り研究室サーバーでホスティングしたい * AWSやLambdaも使えるが,ログが読みにくかったり,他のサービスとの連携が煩わしかったりする。例えば, * DB連携させよう,アカウント作って,ログインして,あれ,なんで繋がらないんだろう(ログなし)うーん。 * 研究室サーバーなら,全ての状態がすぐにわかる。 * 自分にとって効率がいい言語を選べる * Go言語は知的でない僕のような人が扱っても,複雑になりにくく,言語仕様が単純で,記法も細かく定められているので,読みやすいし書きやすい。([Go言語の良いところ](https://www.yunabe.jp/docs/why_golang_is_good.html)) * 引き継ぎ等で障壁が発生するかとも思いましたが,JSは授業で扱っているわけではなさそうなので,どちみち言語を1から学ぶのであれば,どちらでも良いのではないでしょうか。