【Google Apps Script】DiscordのWebhookを使って毎朝天気を通知する(Discord Bot改良:第1回)

Code

はじまり

リサちゃん
リサちゃん

あ〜!

濡れた濡れた・・・

135ml
135ml

お、お帰りさん、

ズブ濡れだねえ

リサちゃん
リサちゃん

今日、夕方から雨が降るなんて

聞いてないよお・・・

135ml
135ml

じゃあ、今回はその日に雨が降るかどうかを
Discordで通知してくれるツールを作ってみよう

リサちゃん
リサちゃん

おおっ。

ツールの概要

今回は、DiscordのWebhookを使って、気象情報をOpenWeatherMapというサイトから

Discord Developer Portal — API Docs for Bots and Developers
Integrate your service with Discord — whether it's a bot or a game or whatever your wildest imagination can come up with...
Сurrent weather and forecast - OpenWeatherMap
Get current weather, hourly forecast, daily forecast for 16 days, and 3-hourly forecast 5 days for your city. Historical...

ツールを作る言語は、Google Apps Scriptを使用します。

APIをタダで作れるため、個人利用の範囲内であれば、とてもコスパが良いです。

ツールの概要はこんな感じ。

機能

Discordに通知するまでの機能の紹介です。

実際に時間トリガーでWebhookが作動するとこのように通知されます。

気温と雨の時間帯を通知するために、以下の情報を取得する必要がありますね。

  • その日の天気(その日の8つの時間帯における天気を集計して最多の天気を取得する)
  • 最高気温
  • 最低気温
  • 任意の時間における天気

気象情報を取得する

まずは、Google Apps Scriptから、OpenWeatherMapのWeather APIにGETでリクエストを送ります。

大体3、4日分くらいを3時間おきでブツ切りされた気象情報がレスポンスで返ってきます。

[ { dt: 1676224800,
    main: 
     { temp: 282.88,
       feels_like: 281.08,
       temp_min: 282.88,
       temp_max: 283.2,
       pressure: 1014,
       sea_level: 1014,
       grnd_level: 1009,
       humidity: 68,
       temp_kf: -0.32 },
    weather: [ [Object] ],
    clouds: { all: 75 },
    wind: { speed: 3.47, deg: 2, gust: 5.47 },
    visibility: 10000,
    pop: 0.42,
    rain: { '3h': 0.43 },
    sys: { pod: 'n' },
    dt_txt: '2023-02-12 18:00:00' },
  { dt: 1676235600,
    main: 
     { temp: 282.84,
       feels_like: 281.32,
       temp_min: 282.75,
       temp_max: 282.84,
       pressure: 1014,
       sea_level: 1014,
       grnd_level: 1008,
       humidity: 66,
       temp_kf: 0.09 },
    weather: [ [Object] ],
    clouds: { all: 83 },
    wind: { speed: 2.95, deg: 7, gust: 4.8 },
    visibility: 10000,
    pop: 0.3,
    sys: { pod: 'n' },
    dt_txt: '2023-02-12 21:00:00' },
.
.
.
]

まずは、その日の天気を決めます。

天気の決め方は、その日の8つの時間帯(0:00、3:00、6:00、9:00、12:00、15:00、18:00、21:00)における天気を集計して、その中で最も多かった天気をその日の天気とします。

天気の種類は、以下のリファレンスに載っています。天気のステータスコードが5xxだと雨になります。

5xxや8xxなどの状態をグループごとに集計して天気を決めました。

Weather Conditions - OpenWeatherMap

おそらく、main属性のオブジェクトにあるtemp_mintemp_maxで、その時間帯の気温を取ってこれます。その気温を1日ごとに集計して、その日の最高気温と最低気温を割り出しました。

最後に、雨の状態になっている時間帯をオブジェクトにメモっておきました。

これで、僕が欲しかった情報は手に入りました。

WebhookでDiscordに通知する

天気の情報を整理してオブジェクト形式に出来たら、それをJSON形式でDiscordのWebhookに送ります。

以下のように送ることで、Discordでメッセージを送信できるかと思います。

muteHttpExceptionsはどちらでも良いような気がします。

ログの見え方が変わるらしいですが、ぶっちゃけ、trueでもfalseでもログの見え方は大して変わると思っていません。

function getReqHeaders(type="json"){
  let contentType = "";
  switch(type){
    case "json":
      contentType = "application/json"
  }
  const requestHeaders = {
    "Content-Type": contentType
  }
  return requestHeaders;
}

const webhookUrl = "WEBHOOKのURL";
let options = {
  method: "POST"
  , payload: JSON.stringify(payload)
  , headers: getReqHeaders("json")
  , muteHttpExceptions : true
}
let res = UrlFetchApp.fetch(webhookUrl, options);

通知内容を凝りたい

通知するところまでは出来たので、通知するときのデザインを確認します。

先程貼った通知の内容にするためには、こんな感じのコードになります。単なる一部抜粋になります。

infoに整理したオブジェクトがあって、それをmapでどーのこーのしています。

最高気温Maxと最低気温Minを並べて表示したかったので、"embeds"属性の"fields"属性を使って並べました。

let embedsArray = keys.map(location => {
  let items = info[location];
  if(items[keyRO]["weather"].length === 0){
    content = "今日は雨が降らないみたいだよ!";
  }else{
    content = "今日は雨が降るみたいだよ!";
    for(let i = 0; i < items[keyRO]["weather"].length; i++){
      console.log(items[keyRO]["time"][i]);
      r = `${items[keyRO]["time"][i]["hour"]}:${items[keyRO]["time"][i]["minute"]}:${items[keyRO]["time"][i]["second"]} - ${items[keyRO]["weather"][i]}`;
      content = `${content}\r${r}`;
    }
  }
  
  let embeds = [];
  for(let i = 0; i < items[keyTF].length; i++){
    imageFileUrl = getImageFileUrl(items[keyWF][i]["weather"], imageFolderUrl);
    author = getDateDesignation(i);
    iconUrl = "https://openweathermap.org/themes/openweathermap/assets/vendor/owm/img/icons/favicon.ico";
    embeds.push({
      "title": items[keyTF][i]["date"]
      , "color": "15494471"
      , "author": {
        "name": author
        , "url": "https://openweathermap.org/city/?q=Tokyo"
        , "icon_url": iconUrl
      }
      , "footer": {
        "text": "OpenWeatherMap"
        , "icon_url": iconUrl
      }
      , "fields": [
        {
          "name"  : "Max"
          , "value" : `${items[keyTF][i]["temp_max"]}`
          , "inline": true
        }
        , {
          "name"  : "Min"
          , "value" : `${items[keyTF][i]["temp_min"]}`
          , "inline": true
        }
      ]
      , "thumbnail": {
        "url" : imageFileUrl
      }
    })
  }
  return embeds;
})

画像にリンクを埋め込むやつを本文中で使えるかどうかを試してみましたが、それは出来ないようです。

let urlSite = "https://openweathermap.org/city/?q=Tokyo";
let urlImage = "https://openweathermap.org/themes/openweathermap/assets/img/logo_white_cropped.png";
ontent = `${content}\r[![OpenWeatherMap](${urlImage})](${urlSite})`;

const payload = {
  "username": "お天気あゆ"
  , "content": content
  , "embeds": returnEmbeds
  // , "tts": true
}

少し詰まったところ

以下、少し詰まったところです。

ICO形式のファイルは表示できない

また、Discord上では.ico拡張子のファイルは表示できないみたいでした。

2つ上に貼ったコード内のiconUrlが該当箇所です。

後ほど、自分で.png形式に変えて別の場所から公開したものをリンクに付けると、ちゃんとファビコンがDiscord上で表示されました。

Number型のままでは、リクエスト時にエラーになる

これは、Discord Webhookとか関係ないかと思いますが、最高気温と最低気温をNumber型のまま、オブジェクトをJSONにしたら、Discordへのリクエストを正常にできませんでした。

そのため、NumberをStringにして、JSON.stringifyしています。

ちなみに、しゃべらせることができる

ちなみに、1つ上に貼ったコード内の”tts”: trueですが、これをリクエストにちゃんと載せると、Discordで表示されたテキストのとおりに喋り始めます。

画像の場合だと、「今日は雨が降るみたいだよ。ぜろじぜろぜろふんぜろぜろびょう・・・」といった具合になります。

使いどころですね・・・

おしまい

リサちゃん
リサちゃん

うわ、通知されてきた!

135ml
135ml

DiscordのWebhookを使ったツールは、

通知するだけなら比較的簡単に作れます。

ぜひ試してみて下さい。

リサちゃん
リサちゃん

他にも何か作れないかなあ

以上になります!

コメント

タイトルとURLをコピーしました