Googleが毎年やっているSanta Trackerを使ってサンタさんが今どこでプレゼントを配っているかを逐次報告してくれるようにしてみました。
Santa TrackerのAPI
色々諦めたこと
- 緯度経度と距離から近くにいるよとかしたかった
- 近くの定義が難しかったのでやめました
- 住んでいるエリアにサンタさんが来たら、
santa claus is comin to town
をSpotifyでながす- Google Assistant APIの実装が必要そうなので1日では無理だったのでやめました
コード
main.go
package main
import (
"context"
"fmt"
"time"
"github.com/ikasamah/homecast"
)
const (
// 自分の住んでいる場所に一番近いエリア
myID = "tokyo"
santaURL = "https://firebasestorage.googleapis.com/v0/b/santa-tracker-firebase.appspot.com/o/route%2Fsanta_en.json?alt=media&2019b"
)
var jst *time.Location
func init() {
var err error
jst, err = time.LoadLocation("Asia/Tokyo")
if err != nil {
jst = time.FixedZone("Asia/Tokyo", 9*60*60)
}
}
func main() {
tk, err := GetTracker(santaURL)
if err != nil {
panic(err)
}
fmt.Println("Start stalking")
for {
r := tk.Current(time.Now())
// なんかも同じメッセージを喋り続けられるとうざいので、ステータスが変わるまでキャッシュする
// キャッシュされてない場合のみメッセージを話させる
if !r.IsCache {
if r.Destination.ID == myID {
// サンタさん キタ━━━(゚∀゚)━━━!!
googlehome("Santa Claus is coming to town!! Santa Claus is coming to town!! Santa Claus is coming to town!!")
// 自分の街についたら通知をやめる
break
} else {
if r.Status == statusDeliver {
// 今サンタさんがプレゼントを配っている場所を通知
googlehome(fmt.Sprintf("Santa Claus is in %s, %s\n", r.Destination.City, r.Destination.Region))
}
}
}
// ループを繰り返す感覚が短すぎるのでちょっとスリープ
time.Sleep(5 * time.Second)
}
fmt.Println("finish")
}
// Google Homeデバイスに任意のメッセージ話させる
func googlehome(msg string) {
fmt.Println(msg)
ctx := context.Background()
devices := homecast.LookupAndConnect(ctx)
for _, device := range devices {
device.Speak(ctx, msg, "en")
}
}
tracker.go
package main
import (
"encoding/json"
"net/http"
"time"
)
const (
statusTakeOff = "Takeoff"
statusMove = "Move"
statusDeliver = "Deliver"
statusFinsish = "Finsish"
)
type APIResponse struct {
Status string `json:"status"`
Language string `json:"language"`
TimeOffset int `json:"timeOffset"`
Fingerprint string `json:"fingerprint"`
Destinations []Destination `json:"destinations"`
}
type Destination struct {
ID string `json:"id"`
Arrival int64 `json:"arrival"`
Departure int64 `json:"departure"`
Population int `json:"population"`
PresentsDelivered int `json:"presentsDelivered"`
City string `json:"city"`
Region string `json:"region"`
Location Location `json:"location"`
}
type Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type Tracker struct {
Destinations []Destination
Cache Cache
}
type Cache struct {
ExpiredAt int64
Status string
Destination Destination
}
type Result struct {
IsCache bool
Status string
Destination Destination
}
func GetTracker(uri string) (result *Tracker, err error) {
resp, err := http.Get(uri)
if err != nil {
return
}
defer resp.Body.Close()
var r APIResponse
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
return
}
return &Tracker{
Destinations: r.Destinations,
}, nil
}
func (tk *Tracker) SetCache(dest Destination, status string, expiredAt int64) {
tk.Cache = Cache{
Destination: dest,
Status: status,
ExpiredAt: expiredAt,
}
}
func (tk *Tracker) Current(tt time.Time) Result {
ts := tt.Unix() * 1000
c := tk.Cache
if c.Status != "" && c.ExpiredAt >= ts {
return Result{
Status: c.Status,
Destination: c.Destination,
IsCache: true,
}
}
home := tk.Destinations[0]
if home.Departure > ts {
return Result{
Status: statusTakeOff,
Destination: home,
}
}
landing := tk.Destinations[len(tk.Destinations)-1]
if landing.Arrival < ts {
return Result{
Status: statusFinsish,
Destination: landing,
}
}
ds := tk.Destinations[1:]
for _, dest := range ds {
if dest.Arrival > ts {
tk.SetCache(dest, statusMove, dest.Arrival-1)
return Result{
Status: statusMove,
Destination: dest,
}
} else if dest.Arrival <= ts && dest.Departure >= ts {
tk.SetCache(home, statusDeliver, dest.Departure)
return Result{
Status: statusDeliver,
Destination: dest,
}
}
}
return Result{
Status: statusFinsish,
Destination: landing,
}
}
テストをしている時点でサンタさんが日本に来てしまったので、やってみたい人は来年以降楽しんでください。