4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【SlackBot】明日は休みだよBotを作ってみた

Last updated at Posted at 2020-04-28

きっかけ

  • 昨今の事情もあり在宅勤務を行っている方は多いと思います
  • そんな中でチームのSlackでこんなやりとりがありました
スクリーンショット 2020-04-29 0.18.53.png
  • 在宅でリモートだと曜日感覚なくなってきますよね
  • 明日が休みだと思うことで生まれる生産性爆上げ日を逃してしまうのはもったいない!ということでStayHomeの暇つぶしも兼ねて次の日が休みの時にSlackに通知してくれるBotを作ってみました

登場人物

できたもの

  • 当日が平日で、翌日が土日祝日の場合、昼の12時にSlackに以下の投稿がされるようにしてみました
スクリーンショット 2020-04-29 0.36.19.png

実装内容

Lambdaの実装

  • Lambdaにデプロイするベースとなるhandler.tsはこんな感じです
    • 今日と明日が平日かどうか判定し、今日が平日で明日が土日祝日の場合はSlackにメッセージを送ります
handler.ts
import * as dayjs from 'dayjs';
import { APIGatewayProxyHandler } from 'aws-lambda';

import { isHoliday } from './holidayUtil';
import { pushMessage } from './slackApi';

export const noticeDayBeforeHoliday: APIGatewayProxyHandler = async () => {
  const today = dayjs();
  const tomorrow = today.add(1, 'day');

  if (await isHoliday(today)) {
    return { statusCode: 200, body: 'Today is Holiday!!!' };
  }
  if (!(await isHoliday(tomorrow))) {
    return { statusCode: 200, body: "Let's work tomorrow" };
  }

  await pushMessage({ text: '明日は休みだよ!残りの仕事も頑張ろう!' });
  return { statusCode: 200, body: 'Tomorrow is Holiday!!!' };
};

土日祝日判定

  • 日本の祝日情報を取得するAPIがないかと検索したらHolidays JP APIを見つけたので使ってみました
    • 以下のような形式で祝日一覧を返してくれるAPIです
{
  "2017-01-01": "元日",
  "2017-01-02": "元日 振替休日",
  "2017-01-09": "成人の日"
}
  • 祝日判定をしているholidayUtil.tsのコードはこんな感じです
    • 引数でもらった日付を土曜/日曜か判定し、どちらでもなければ Holidays JP API から取得した祝日に該当するかチェックしています
holidayUtil.ts
import fetch from 'node-fetch';
import * as dayjs from 'dayjs';

const getHolidays = async () => {
  const res = await fetch('https://holidays-jp.github.io/api/v1/date.json');
  return res.json();
};

export const isHoliday = async (date: dayjs.Dayjs) => {
  const formattedDate = date.format('YYYY-MM-DD');

  if (date.day() === 0) {
    console.log(`${formattedDate} is Sunday`);
    return true;
  }

  if (date.day() === 6) {
    console.log(`${formattedDate} is Saturday`);
    return true;
  }

  const holidays = await getHolidays();

  if (Object.keys(holidays).includes(formattedDate)) {
    console.log(`${formattedDate} is Holiday`);
    return true;
  }

  console.log(`${formattedDate} is Weekday`);
  return false;
};

Slackへの通知

  • Slackへの通知は公式が提供する@slack/web-apiを使いました
  • Slackへ通知を送るslackApi.tsはこんな感じです
    • Tokenはリポジトリに含めたくない、投稿先チャンネルはdevとprodで差し替えたいといった事情から、それらの情報は環境変数から取得しています
slackApi.ts
  
import { WebClient } from '@slack/web-api';

const { SLACK_API_TOKEN, SLACK_CHANNEL, SLACK_USERNAME } = process.env;

const slackClient = new WebClient(SLACK_API_TOKEN);

export const pushMessage = async ({ text }) => {
  const result = await slackClient.chat.postMessage({
    text,
    channel: SLACK_CHANNEL,
    username: SLACK_USERNAME,
    unfurl_links: true,
  });
  console.log({ result });
};

ServerlessFrameworkの設定

  • ServerlessFrameworkの設定ファイルであるserverless.ymlはこんな感じです
    • 上の説明でも出てきた環境変数周りの設定やLambdaの定期実行のためのschedule設定などをしています
serverless.yml
service:
  name: day-before-holiday
custom:
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true
  defaultStage: dev
  slack:
    dev:
      token: ${env:SLACK_API_TOKEN_DEV}
      channel: ${env:SLACK_CHANNEL_DEV}
      username: ${env:SLACK_USERNAME_DEV}
    production:
      token: ${env:SLACK_API_TOKEN_PROD}
      channel: ${env:SLACK_CHANNEL_PROD}
      username: ${env:SLACK_USERNAME_PROD}
  schedule:
    noticeDayBeforeHoliday:
      dev: # スケジュールなし
      production: cron(0 3 * * ? *) # 日本時間で毎日昼12時
plugins:
  - serverless-webpack
  - serverless-dotenv-plugin
provider:
  name: aws
  runtime: nodejs12.x
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  environment:
    AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
    TZ: Asia/Tokyo
    SLACK_API_TOKEN: ${self:custom.slack.${self:provider.stage}.token}
    SLACK_CHANNEL: ${self:custom.slack.${self:provider.stage}.channel}
    SLACK_USERNAME: ${self:custom.slack.${self:provider.stage}.username}
functions:
  noticeDayBeforeHoliday:
    handler: src/handler.noticeDayBeforeHoliday
    events:
      - schedule: ${self:custom.schedule.noticeDayBeforeHoliday.${self:provider.stage}}

Local/Dev/Prodの使い分け

  • ちょっとしたLambdaの開発ですが本番環境Onlyで突っ走るのは抵抗があります
  • 今回はローカルとAWS(Dev環境)とAWS(Prod環境)に分けて動作確認をしていきました
    • 基本はローカルで開発を進める
    • 完成したらDevでテスト
    • OKならProdへデプロイ
  • package.jsonに定義したsrciptsはこんな感じです
package.json
  "scripts": {
    "invoke": "sls invoke local -f noticeDayBeforeHoliday",
    "invoke:aws": "sls invoke -f noticeDayBeforeHoliday",
    "deploy": "sls deploy",
    "deploy:prod": "sls deploy --stage production"
  }

Local

  • ローカルでは以下のコマンドで実行することができます
yarn invoke

Dev

  • AWSにDevモードでデプロイする時は以下のコマンドです

yarn deploy
  • AWSにデプロイしたLambdaを実行する時は以下のコマンドです
yarn invoke:aws

Prod

  • AWSにProdモードでデプロイする時は以下のコマンドです

yarn deploy:prod
  • Prodを暴発させるのは良くないのでProdのLambdaを実行するコマンドは入れませんでした

まとめ

  • ちょっとしたLambda関数の開発でしたがServerlessFrameworkが快適なので作ってて楽しかったです
  • これで在宅勤務でも祝前日の生産性爆上げ日を確保できそうですね
  • StayHomeが続きますが工夫して乗り切りましょう!
4
0
0

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
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?