# Alexa x SpreadSheet連携
###### tags: `研究`
## イベント通知
### 概要
スプレッドシート上に入力されたイベント情報を,Alexaで通知します。とりあえずなのでソースコードが雑です。

### 実装
このデータは,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から学ぶのであれば,どちらでも良いのではないでしょうか。