---
# System prepended metadata

title: Alexa x SpreadSheet連携
tags: [研究]

---

# 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から学ぶのであれば，どちらでも良いのではないでしょうか。