Go(golang)で気圧低下をお知らせするLINE botを作ってみた

Goを使って、気圧低下をお知らせするLINE botを作ってみました。
1時間に1回、気圧の状態をチェックして、注意・警戒の場合は状態の場合は通知するようになっています。

![todomaru_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2066989/dc783a8d-e94c-b836-e807-e9e163122cfe.png)
（トド丸くんというのは我が家でいろいろお知らせ通知をしてくれる子です
　最初はtodoリストを通知するのが目的だった子なので、トド（todo）丸です）
**※今回はグループにLINE botを招待し、そこで通知する仕組みにしているのですこし特殊です**

今回実装したアプリのソースコードは以下にて公開しています

https://github.com/chasegawa1209/linebot-notify-barometric-pressure


## LINE developersの設定
[LINE developers 公式サイト](https://developers.line.biz/ja/)

https://developers.line.biz/ja/docs/messaging-api/getting-started/

1. 上記のLINE developers の公式ドキュメント「Messaging APIを始めよう」に則ってチャンネル作成をします

1. チャンネルを開き、「チャンネル基本設定」を開きます（デフォルトで開かれます）
1. 基本情報内のチャネルシークレットをメモしておきます
1. 「Messaging API設定」を押下します
![todomaru_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2066989/35309751-4fe6-cb40-4688-783bcc77ad88.png)

1. 一番下までいくと、「チャンネルアクセストークン」という項目があるので、そこの「発行」ボタンを押下します
1. チャンネルアクセストークンが表示されるのでメモしておきましょう


## GitHubリポジトリの作成
GitHubリポジトリを作成します
"Add .gitignore" にチェックを入れ、"Go"を選択し、リポジトリを作成します


## アプリの実装

ここからとにかく実装します
全体のコードは冒頭で紹介した[GitHubのリポジトリ](https://github.com/chasegawa1209/linebot-notify-barometric-pressure)にあげてあるのでそちらをみていただけたらと思います
（レイヤードアーキテクチャで実装しているので、少し読みにくい部分があるかもしれません。ご了承ください。。。）

ここでは、LINE botでのメッセージ配信部分と、気圧情報取得部分のみご紹介します

### LINE botでメッセージを配信する
メッセージ配信部分のメソッドはこちらになります。
**※今回はグループにLINE botを招待し、そこで通知する仕組みにしているのでルームIDが必要になります**
　ルームIDの取得方法はこちらを参考にしました
　参考：[トークルームの roomId を調べる方法 - Qiita](https://qiita.com/kunihiros/items/6a636cbccebdf86af225)

``` go
func (s *LineStore) Post(message string) error {
    _, err := s.bot.PushMessage(s.roomID, linebot.NewTextMessage(message)).Do()
    if err != nil {
        s.logger.Error(err.Error())
        return err
    }
    return nil
}
```

参考：[Messaging APIリファレンス | LINE Developers](https://developers.line.biz/ja/reference/messaging-api/#send-push-message)

### 気圧情報を「頭痛ーる」から取得する
今回は、株式会社ポッケさんの頭痛ーるを利用させていただきます

https://zutool.jp/

そして、こちらのリンクを大いに参考にさせていただきました
ありがとうございます
**参考：[低気圧で頭痛がひどいのでslackで頭痛ーるを表示するappをlambdaで作った](https://zenn.dev/shuzon/articles/8ce0d4bafea9e28d9edd)**

試しに東京都新宿区の気圧情報を取得してみます

``` sh
$ curl https://zutool.jp/api/getweatherstatus/13104 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  8261    0  8261    0     0  41722      0 --:--:-- --:--:-- --:--:-- 41512
{
  "place_name": "東京都新宿区",
  "place_id": "104",
  "prefectures_id": "13",
  "dateTime": "2021-09-24 13",
  "yesterday": [
    {
      "time": "0",
      "weather": "200",
      "temp": "25.9",
      "pressure": "1009.8",
      "pressure_level": "2"
    },
    {
      "time": "1",
      "weather": "300",
      "temp": "25.4",
      "pressure": "1009.6",
      "pressure_level": "2"
    },
    ...
    {
      "time": "22",
      "weather": "100",
      "temp": "18",
      "pressure": "1021.9",
      "pressure_level": "2"
    },
    {
      "time": "23",
      "weather": "100",
      "temp": "17.5",
      "pressure": "1021.5",
      "pressure_level": "2"
    }
  ]
}
```

URLの数字部分が、地域を特定するIDになっています
地域IDは [こちら](https://www.soumu.go.jp/denshijiti/code.html) で確認することができます

レスポンスは、指定した地域に対して、昨日・今日・明日の0~23時の気圧情報となっています
今回は、今日の現在時刻・翌時間・翌々時間の 気圧レベル を使用していきます

気圧レベルはマジックナンバーで定められているので、 [こちら](https://zenn.dev/shuzon/articles/8ce0d4bafea9e28d9edd#%E5%A4%A9%E6%B0%97%E3%81%A8%E6%B0%97%E5%9C%A7%E3%83%AC%E3%83%99%E3%83%AB) の対応表を参考にしました

気圧情報が取得できたら、気圧レベルが通知対象になるかチェックします
通知対象になっていた場合はメッセージを生成します

``` barometric_pressure__model.go
package model

import (
    "github.com/kyokomi/emoji"
)

const (
    PressureLevelIntOK0             = iota
    PressureLevelIntOK1
    PressureLevelIntSomewhatWarning
    PressureLevelIntWarning
    PressureLevelIntBomb
)

const (
    PressureLevelStrOK              = "通常"
    PressureLevelStrSomewhatWarning = "やや注意"
    PressureLevelStrWarning         = "注意"
    PressureLevelStrBomb            = "警戒"
)

var PressureLevelMap = map[int]string {
    PressureLevelIntOK0:             PressureLevelStrOK,
    PressureLevelIntOK1:             PressureLevelStrOK,
    PressureLevelIntSomewhatWarning: emoji.Sprint(":arrow_heading_down:") + PressureLevelStrSomewhatWarning,
    PressureLevelIntWarning:         emoji.Sprint(":warning:") + PressureLevelStrWarning,
    PressureLevelIntBomb:            emoji.Sprint(":bomb:") + PressureLevelStrBomb,
}

type BarometricPressure struct {
    NowLevel        int
    After1HourLevel int
    After2HourLevel int
}

type BarometricPressuresByZutool struct {
    PlaceName        string                        `json:"place_name"`
    PlaceID          string                        `json:"place_id"`
    PrefecturesID    string                        `json:"prefecture_id"`
    DateTime         string                        `json:"dateTime"`
    Yesterday        []*BarometricPressureByZutool `json:"yesterday"`
    Today            []*BarometricPressureByZutool `json:"today"`
    Tomorrow         []*BarometricPressureByZutool `json:"tomorrow"`
    DayAfterTomorrow []*BarometricPressureByZutool `json:"dayaftertomorrow"`
}

type BarometricPressureByZutool struct {
    Time          string `json:"time"`
    Weather       string `json:"weather"`
    Temp          string `json:"temp"`
    Pressure      string `json:"pressure"`
    PressureLevel string `json:"pressure_level"`
}

```

``` usecase.go
func (u *Usecase) Exec() *UsecaseResult {
    result := &UsecaseResult{}
    now := time.Now()
    defer func() {
        result.ProcessingTime = time.Since(now).Seconds()
    }()

    // 気圧取得
    barometricPressure, err := u.repository.GetBarometricPressure(now.Hour())
    if err != nil {
        result.Err = err
        return result
    }

    // 警戒レベルがあるかチェック
    if barometricPressure.NowLevel > model.PressureLevelIntSomewhatWarning || barometricPressure.After1HourLevel > model.PressureLevelIntSomewhatWarning || barometricPressure.After2HourLevel > model.PressureLevelIntSomewhatWarning {
        // メッセージ作成
        message := createMessage(barometricPressure)

        // LINEBotで送信
        if err := u.repository.PostMessage(message); err != nil {
            result.Err = err
            return result
        }
    }

    return result
}

func createMessage(barometricPressure *model.BarometricPressure) string {
    levelStr := convertPressureLevel(barometricPressure)
    messageFormat := "気圧が下がってきてるまる…！\n薬の準備をして、無理せず休むまるよ〜。。。\n\n"
    messageFormat = messageFormat + "現在：%s\n1時間後：%s\n2時間後：%s"

    message := fmt.Sprintf(
        messageFormat,
        levelStr[0],
        levelStr[1],
        levelStr[2],
    )
    return message
}

func convertPressureLevel(barometricPressure *model.BarometricPressure) []string {
    var levelStr []string
    levelStr = append(levelStr, model.PressureLevelMap[barometricPressure.NowLevel])
    levelStr = append(levelStr, model.PressureLevelMap[barometricPressure.After1HourLevel])
    levelStr = append(levelStr, model.PressureLevelMap[barometricPressure.After2HourLevel])
    return levelStr
}

```

## Herokuにデプロイ

Heroku CLIを利用するので準備しておきましょう

https://devcenter.heroku.com/ja/articles/heroku-cli

### デプロイ
#### Herokuアプリケーションの作成
以下コマンドでHerokuアプリケーションを作成します

``` sh
$ create heroku linebot-barometric-pressure
```

参考：[CLI からのアプリの作成 | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/creating-apps)

#### 環境変数の設定
LINE MessagingAPIのシークレット、アクセスキー、ルームIDと、頭痛ーるの場所IDを設定します

``` sh
$ heroku config:set LINE_ACCESS_TOKEN <チャンネルトークン> 
$ heroku config:set LINE_SECRET <チャンネルシークレット>
$ heroku config:set LINE_ROOM_ID <ルームID>
$ heroku config:set PLACE_ID <頭痛ーる 場所ID>
```

参考：[Heroku CLI コマンド | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/heroku-cli-commands#heroku-config-set)

#### go.modにコメントを追加
 `go.mod` にHerokuデプロイ用コメントを追加します

``` go.mod
module github.com/chasegawa1209/linebot-notify-barometric-pressure

// 以下はHerokuデプロイ用コメント
// +heroku goVersion go1.16
go 1.16

require (
	github.com/kyokomi/emoji v2.2.4+incompatible
	github.com/line/line-bot-sdk-go v7.8.0+incompatible
	go.uber.org/zap v1.17.0
)

```

#### デプロイ
herokuにリモートリポジトリを登録します

``` sh
$ heroku git:remote -a linebot-barometric-pressure
$ git remote -v
heroku  https://git.heroku.com/linebot-barometric-pressure.git (fetch)
heroku  https://git.heroku.com/linebot-barometric-pressure.git (push)
origin  git@github.com:chasegawa1209/linebot-notify-barometric-pressure.git (fetch)
origin  git@github.com:chasegawa1209/linebot-notify-barometric-pressure.git (push)
```

gitへのpushと、Herokuへのpushを行います

``` sh
$ git push origin main
$ git push heroku main
```

#### 動作確認
アプリが正常に動くか動作確認をしてみます

``` sh
$ heroku run ./bin/linebot-notify-barometric-pressure
 ›   Warning: heroku update available from 7.54.0 to 7.59.0.
Running ./bin/linebot-notify-barometric-pressure on ⬢ linebot-barometric-pressure... up, run.2996 (Free)
{"level":"\u001b[34minfo\u001b[0m","time":"2021-09-24T04:01:55.413Z","caller":"build_dd2f39ba/app.go:29","msg":"ProcessingTime: 0.770953[s]"}
{"level":"\u001b[34minfo\u001b[0m","time":"2021-09-24T04:01:55.415Z","caller":"build_dd2f39ba/app.go:33","msg":"success to NotifyBarometricPressureBatch"}
```

正常に動きました🎉

### 定期実行
最後に、定期的に気圧情報をチェックするように定期実行の設定をします

無料のスケジューラーアドオンがあるのでCLIから設定します

```
$ heroku addons:create scheduler
```

スケジューラーの設定画面がブラウザで開きます
右上の "Add Job" を押下すると "Job Editor" が開くのでスケジュールを設定します
今回は
   Schedule: **Every hour at ... :00**
Run Command: **./bin/linebot-notify-barometric-pressure**
に設定します
（Run Commandは `./bin/<アプリ名>` になります）

これで1時間に1回、気圧情報をチェックし、注意以上の場合はLINEに通知されるようになります

## おわりに
今後は、自宅付近に雨雲が近づいたら通知してくれる機能を追加していこうと思います
少しでも役に立った、いいね👍、と感じましたら "LGTM" していただけるととてもうれしいです