LoginSignup
15
11

More than 5 years have passed since last update.

GoogleAppScriptを使ってDiscordに朝と夕方に天気予報を流す

Posted at

概要

友人が「天気予報をDiscordに流してくれると便利かも」と言っていたのでGoogleAppScript(以下GAS)を利用して作成してみた。
朝には今日の天気予報を、夕方には明日の天気予報を投稿する。
リポジトリはproudust/weather-bot-for-discord

環境設定

GASなので当然howdy39/gas-clasp-starterをベースに作成を始める。

折角なので(?)TSLintを削除して@typescript-eslint/eslint-pluginに切り替えた。
まだ対応が完全ではないのか、@types/google-apps-scriptで宣言されているグローバル変数declare var UrlFetchAppなどを認識してくれないようなので、selectnull/eslint-plugin-googleappsscriptを入れてお茶を濁す。

npm uninstall tslint tslint-config-prettier tslint-plugin-prettier
npm i --save-dev eslint eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin eslint-plugin-googleappsscript
.eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended"
  ],
  "plugins": [
    "@typescript-eslint",
    "googleappsscript",
    "jest",
    "prettier"
  ],
  "env": {
    "googleappsscript/googleappsscript": true,
    "jest/globals": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "rules": {
    "@typescript-eslint/indent": "off",
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": true,
        "printWidth": 100
      }
    ]
  }
}

天気予報の取得

気象情報API比較してみたを参考に、最高気温と最低気温がちゃんと取れるDark Sky API(元Forecast)を採用した。
さくっとアカウント登録を済ませ、試しにAPIを叩く。

curl --request GET \
  --url 'https://api.darksky.net/forecast/$KEY/$LATITUDE,$LONGITUDE?exclude=currently,minutely,hourly,flags&lang=ja&units=si'

パラメータの意味
- $KEY アカウント登録後に表示されるSecret Key
- $LATITUDE,$LONGITUDE 緯度、経度
- ?exclude=currently,minutely,hourly,flags レスポンスから除外する情報を指定する(これらを除外すると1週間分の天気予報だけが残る)
- &lang=ja 解説などに使用される言語を日本語に設定
- &units=si 単位を摂氏・メートル法に設定

型定義の作成

レスポンスをjson2tsに突っ込み、自動生成された型定義を微調整する。
当然リテラル型は認識してくれないので自分で書く。
それぞれの値の意味は公式Dark Sky APIを使ってみました!を参考にした。

darksky.ts
type DarkSkyIcon =
  | 'clear-day'
  | 'clear-night'
  | 'rain'
  | 'snow'
  | 'sleet'
  | 'wind'
  | 'fog'
  | 'cloudy'
  | 'partly-cloudy-day'
  | 'partly-cloudy-night';

interface DarkSkyApiResponse {
  latitude: number;
  longitude: number;
  timezone: string;
  daily: {
    summary: string;
    icon: DarkSkyIcon;
    data: {
      time: number;
      summary: string;
      icon: DarkSkyIcon;
      sunriseTime: number;
      sunsetTime: number;
      moonPhase: number;
      precipIntensity: number;
      precipIntensityMax: number;
      precipIntensityMaxTime: number;
      precipProbability: number;
      temperatureHigh: number;
      temperatureHighTime: number;
      temperatureLow: number;
      temperatureLowTime: number;
      apparentTemperatureHigh: number;
      apparentTemperatureHighTime: number;
      apparentTemperatureLow: number;
      apparentTemperatureLowTime: number;
      dewPoint: number;
      humidity: number;
      pressure: number;
      windSpeed: number;
      windGust: number;
      windGustTime: number;
      windBearing: number;
      cloudCover: number;
      uvIndex: number;
      uvIndexTime: number;
      visibility: number;
      ozone: number;
      temperatureMin: number;
      temperatureMinTime: number;
      temperatureMax: number;
      temperatureMaxTime: number;
      apparentTemperatureMin: number;
      apparentTemperatureMinTime: number;
      apparentTemperatureMax: number;
      apparentTemperatureMaxTime: number;
      precipType?: 'rain' | 'snow' | 'sleet';
    }[];
  };
  offset: number;
}

GASでRESTAPIを叩く場合はUrlFetchApp.fetch()を用いる。
SECRETKEYはソースコードに含めず、スクリプトのプロパティから読み込む。

darksky.ts
const key = PropertiesService.getScriptProperties().getProperty('SECRETKEY');
const apiurl = `https://api.darksky.net/forecast/${key}/${latitude},${longitude}?exclude=currently,minutely,hourly,flags&lang=ja&units=si`;
try {
  const response = UrlFetchApp.fetch(apiurl).getContentText('UTF-8');
  return JSON.parse(response);
} catch (error) {
  Logger.log(JSON.stringify(error));
  throw error;
}

Webhookを叩いてDiscordに投稿

URLの払い出しは公式を見てもらうとして、データを投稿用に加工する。
Webhookで渡すパラメータはDiscordにWebhookでいろいろ投稿するを参考にした。

interface DiscordWebhookPayload {
  username?: string;
  avatar_url?: string;
  content?: string;
  embeds?: [
    {
      title?: string;
      description?: string;
      url?: string;
      timestamp?: string;
      color?: number;
      footer?: {
        text?: string;
        icon_url?: string;
      };
      image?: {
        url?: string;
      };
      thumbnail?: {
        url?: string;
      };
      author?: {
        name?: string;
        url?: string;
        icon_url?: string;
      };
      fields?: {
        name?: string;
        value?: string;
        inline?: boolean;
      }[];
    }
  ];
}
const daily = forecast.daily.data[numberOfDays];
const date = new Date(daily.time * 1000);

const payload: Discord.DiscordWebhookPayload = {
  avatar_url: wearherIconUrl[daily.icon],
  embeds: [
    {
      title: `${date.getMonth() + 1}${date.getDate()}日の天気`,
      description: `**${daily.summary}**`,
      url: `https://darksky.net/forecast/${forecast.latitude},${forecast.longitude}/si12/ja`,
      fields: [
        {
          name: '最高気温',
          value: `${daily.temperatureMax}℃`,
          inline: true
        },
        {
          name: '最低気温',
          value: `${daily.temperatureMin} ℃`,
          inline: true
        },
        {
          name: '湿度',
          value: `${Math.round(daily.humidity * 100)}% `,
          inline: true
        },
        {
          name: '降水確率',
          value: `${Math.round(daily.precipProbability * 100)}% `,
          inline: true
        }
      ]
    }
  ]
};

変換したデータをでWebhookにPOSTする。WebhookのURLもスクリプトのプロパティから読み込む。
URLFetchRequestOptions.payloadobjectも入れられるが、勝手にjsonに変換してくれるわけではないので変換してから渡す。
また、content-typeの設定を間違えると意味不明なエラーしか返してくれないので要注意。

  const url = PropertiesService.getScriptProperties().getProperty('WEBHOOK');
  const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
    method: 'post',
    contentType: 'application/json;multipart/form-data;application/x-www-form-urlencoded',
    payload: JSON.stringify(payload)
  };
  try {
    UrlFetchApp.fetch(url, options);
  } catch (error) {
    Logger.log(JSON.stringify(error));
    return;
  }

GASにソースコードをアップロード

G Suite Developer Hubにアクセスし、新しいスクリプトを作成する。
UrlFetchApp.fetchにはhttps://www.googleapis.com/auth/script.external_requestの権限が必要なのでappsscript.jsonに追記しておく。
claspの扱いはGoogle Apps Script をローカル環境で快適に開発するためのテンプレートを作りましたを参考にした。

appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "exceptionLogging": "STACKDRIVER"
}

トリガーの設定

AppScriptダッシュボードから作成したスクリプトの右端にあるボタンをクリックし、トリガーを選ぶと管理画面が開く。
右下のトリガーを追加を選ぶと新しいトリガーの追加ができるので、毎日6~7時と18時~19時に設定する。

2019-03-09_184445.png

完成品

ちょっと寂しい気もするが、最低限欲しい情報は得られるので良しとした。
またテンプレートに含まれていたJestを全く使っていないので、次何か作るときは活用するようにしたい。
ソースコードはこちらproudust/weather-bot-for-discord
2019-03-09_184902.png

参考

15
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11