脱MTG忘れ ~Google APIとSlackWebhookで自動リマインダーを構築してみた~
はじめに
毎朝仕事の始まりは、その日のスケジュールを確認して、MTG 開始時刻の数分前にアラームをかける作業を行っている方は多いと思います。
特別大変な作業でもないので、私も毎朝続けてきましたが、頭のどこかでアラームのかけ忘れはないか?
MTGまであと何分だ?
と心配事を常にぼんやり抱えている状態でした。
今回は、そんな心配事を取り除き作業に集中できる環境を作る為に、当日のスケジュール確認
と開始2分前のリマインド
を自動化してみました。
もし、同じ様な思いをしている方は、初期設定だけちょっと大変ですが、ぜひ実施してみてください。
github で公開しているので、よければご利用ください。
プログラムの全体像
このプログラムで出来ること
このプログラムは、OAuth2.0 を用いて Google Calendar API にアクセスし、登録されたミーティングの予定を取得して、以下の内容を Slack に通知します。
-
朝一、当日のスケジュール一覧通知
-
MTG 開始 2 分前のリマインド通知
また、プログラムの実行は cron を利用して、shell script から自動的に実行しているので、作業漏れが発生しません。
このプログラムで出来ないこと(注意点)
現状では、以下に該当した場合リマインドの通知がされません。
- PC 再起動後すべてのリマインド通知
- cron で./script.sh を実行した時間以降に
追加登録されたスケジュール
のリマインド通知
利用している技術
- GoogleCalendarAPI
- OAuth2.0
- SlackWebhook
- shell script
- Node.js
- cron
フォルダ構成と機能説明
📁 フォルダ構成
app
├── .env
├── README.md
├── config
│ ├── credentials.json
│ └── token.json
├── eslint.config.mjs
├── logs
│ └── cron.log
├── package-lock.json
├── package.json
├── script.sh
└── src
├── googleAuth.js
└── mtgNotif.js
🔐 認証機能 (googleAuth.js)
OAuth2.0 を使用して Google Calendar API にアクセスするための認証処理を実装しています。
保存された認証情報(token.json)を読み込み、必要に応じて credentials.json の値を利用して、新しい認証情報を取得し token.json として保存します。
const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const { authenticate } = require('@google-cloud/local-auth');
const { google } = require('googleapis');
// スコープの設定
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
// Pathの設定
const TOKEN_PATH = path.join(process.cwd(), 'config', 'token.json');
const CREDENTIALS_PATH = path.join(process.cwd(), 'config', 'credentials.json');
/**
* 保存された認証情報を読み込む関数
*
* @return {Promise<OAuth2Client|null>}
*/
async function loadSavedCredentialsIfExist() {
try {
// トークンファイルを読み込む
const content = await fs.readFile(TOKEN_PATH);
// 読み込んだ内容をJSONとして解析
const credentials = JSON.parse(content);
// Googleの認証オブジェクトを生成
const res = google.auth.fromJSON(credentials);
return res; // 認証オブジェクトを返す
} catch {
return null; // エラーが発生した場合はnullを返す
}
}
/**
* 認証情報を保存する関数
*
* @param {OAuth2Client} client
* @return {Promise<void>}
*/
async function saveCredentials(client) {
// credentials.jsonを読み込み
const content = await fs.readFile(CREDENTIALS_PATH);
// 読み込んだ内容をJSONとして解析
const keys = JSON.parse(content);
// 使用する認証情報を選択(インストールされたアプリの情報か、ウェブアプリの情報か)
const key = keys.installed || keys.web;
// 保存するためのペイロードを作成
const payload = JSON.stringify({
type: 'authorized_user',
/* eslint-disable camelcase */
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
/* eslint-enable camelcase */
});
// ペイロードをトークンファイルに保存
await fs.writeFile(TOKEN_PATH, payload);
}
/**
* APIを呼び出すための認証を行う関数
*
* @return {Promise<OAuth2Client>}
*/
async function authorize() {
let client = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
}
// authorize関数とgenerateAuthUrl関数をエクスポート
module.exports = {
authorize
};
🔔 予定取得と通知機能 (mtgNotif.js)
mtgNotif.js ファイルでは、googleAuth.js で認証された OAuth Client を利用して、Google Calendar から当日のスケジュールを取得します。
取得したスケジュールを整形し、その日の MTG 一覧を Slack へ通知します。
また、各ミーティングの開始 2 分前にも、Slack でリマインダー通知を行います。
const { IncomingWebhook } = require('@slack/webhook');
const { authorize } = require('./googleAuth.js');
const { google } = require('googleapis');
const dayjs = require('dayjs');
const cron = require('node-cron');
require('dotenv').config();
// SlackのWebhook URLを環境変数から取得
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
const webhook = new IncomingWebhook(webhookUrl);
// Google Calendar APIからイベント(Schedule)を取得する関数
async function fetchTodayMtgSchedules() {
try {
// googleAuth.jsからexportしたauthorize関数を使ってOAuth2クライアントを取得
const auth = await authorize();
// Google Calendar APIのセットアップ
const calendar = google.calendar({ version: 'v3', auth });
// 今日の日付を設定
const today = new Date();
const startOfDay = new Date(today.setHours(0, 0, 0, 0)).toISOString();
const endOfDay = new Date(today.setHours(23, 59, 59, 999)).toISOString(); // eslint-disable-line no-magic-numbers
// Google Calendar APIからイベント(Schedule)の取得
const response = await calendar.events.list({
calendarId: process.env.CALENDAR_ID,
timeMin: startOfDay,
timeMax: endOfDay,
singleEvents: true,
orderBy: 'startTime',
});
// イベント(Schedule)の整形
const events = response.data.items;
const result = events.map(event => ({
summary: event.summary,
dateTime: new Date(event.start.dateTime).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) // 日本時間に変換
}));
return result;
} catch (error) {
console.log(`スケジュールの取得に失敗しました: ${error}`); // eslint-disable-line no-console
return [];
}
}
// Slackに通知する関数
async function sendMtgNotification(events) {
try {
// result(Scheduleの内容)を整形して、Slackに通知
if (events.length) {
const formattedEvents = events.map(event => `\t・ ${event.dateTime} - ${event.summary}`).join('\n');
await webhook.send({
text: `【本日のMTG予定】\n${formattedEvents}`
});
// Reminderの設定
scheduleReminder(events);
} else {
await webhook.send({
text: '【本日のMTG予定】\n 本日MTGの予定はありません'
});
}
} catch (error) {
await webhook.send({
text: `通知送信中にエラーが発生しました: ${error}`
});
}
}
// 指定された会議のn分前に通知する関数
function scheduleReminder(events) {
events.forEach(event => {
const today = dayjs().format('YYYY-MM-DD'); // 今日の日付を取得
const eventDateTime = `${today} ${event.dateTime}`; // 今日の日付に時間を結合
const eventTime = dayjs(eventDateTime, 'YYYY-MM-DD HH:mm'); // 結果をdayjsオブジェクトに変換
const reminderMinutes = 2; // 会議の何分前に通知するかを変数に格納
const notifyTime = eventTime.subtract(reminderMinutes, 'minute'); // 通知する時間を計算
const cronTime = `${notifyTime.minute()} ${notifyTime.hour()} * * *`; // cron時間を設定
cron.schedule(cronTime, () => {
// 非同期即時実行関数(IIFE)を使って、非同期処理をその場で実行
(async function () {
try {
// 非同期処理を行う(例: Slackへのメッセージ送信)
await webhook.send({
text: `"${event.summary}" が2分後に始まります`
});
} catch {
await webhook.send({
text: `Reminder送信中にエラーが発生しました: "${event.summary}"`
});
}
})(); // ここで関数を定義すると同時に実行している
});
});
};
// toDayMtgNotif関数をリファクタリング
async function toDayMtgNotif() {
try {
const events = await fetchTodayMtgSchedules();
await sendMtgNotification(events);
} catch (error) {
await webhook.send({
text: `toDayMtgNotifの実行に失敗しました: ${error}`
});
}
}
toDayMtgNotif();
🤖 自動実行機能 (script.sh)
crontab で登録した実行日時に、src/mtgNotif.js を実行する為のスクリプトです。
Slack の起動もこのスクリプトの中で実行しています。
#!/bin/bash
# Slackが起動していない場合、Slackを起動
if ! pgrep -x "Slack" > /dev/null; then
echo "Starting Slack..."
open -a "Slack" # Slackを起動
sleep 5 # 完全に起動するまで少し待つ
fi
# プロセスを停止(前日のプロセスを終了)
pid=$(ps aux | grep 'src/mtgNotif.js' | grep -v grep | awk '{print $2}')
if [ -n "$pid" ]; then
kill "$pid"
echo "Stopped process: $pid"
fi
cd ~/projects/NotifMTG
# mtgNotifをバックグラウンドで実行
/opt/homebrew/bin/node ~/projects/NotifMTG/src/mtgNotif.js &
cron の設定
平日 8:45 に実行する場合の crontab のサンプル
45 8 * * 1-5 /bin/zsh -c 'source ~/projects/NotifMTG/script.sh' >> ~/projects/NotifMTG/logs/cron.log 2>&1
利用前の環境設定
事前に以下作業を実施する必要があります。
- git clone と npm install
- Google API の利用準備
- Google Calendar の ID を確認
- SlackWebhookURL の取得
- cron の設定
1. git clone と npm install
Terminal で任意のディレクトリに移動して以下コマンドを一行ずつ実行
git clone https://github.com/naoki0803/NotifMTG.git
npm install
2. Google API の利用準備
ダウンロードした json ファイルの名前を、credentials.json
に変更して、config フォルダ内に保存します。
1. OauthClient のスコープ
記事内の3. OAuth同意を構成
の手順の手順 5 の画面で、スコープを選択しますが、
今回のプログラムの場合auth/calendar.readonly
だけ選択いただければ動きます。
2. 承認済みリダイレクト URI の記述
記事内の4. アクセス認証情報を作成
の手順 3 の画面にある
承認済みのリダイレクト URI にhttp://localhost:3000/callback
と入力して作成をクリックしてください。
3. JSON ファイルのダウンロードと保存
上記画像の作成を押すと json のダウンロードが出来ますので、ファイルの名前を、credentials.json
に変更して、config フォルダ内に保存します。
app
├── .env
├── config
│ └── credentials.json #ここに保存します
└── src
├── googleAuth.js
└── mtgNotif.js
3. Google Calendar の ID 確認
- MTG のスケジュールなどを入力している Google Calendar の ID を確認して、
.env
ファイルに入力します。
CALENDAR_ID=カレンダーIDを入力する
app
├── .env
├── config
│ └── credentials.json
└── src
├── googleAuth.js
└── mtgNotif.js
4. SlackWebhookURL の取得
-
以下 URL に接続して
Go to Your Apps
をクリック
https://api.slack.com/quickstart
-
Create New App
をクリック
-
下段の
From scratch
をクリック
-
任意の
AppName
の入力と、通知を送信する Slack のWorkspace
を選択して Create App をクリック
-
作成された App のサイドバーから
Incoming Webhooks
を選択して、Activate Incoming Webhooks をOn
に変更する。
-
同じ画面下部にある
Add New Webhook to Workspace
をクリック
-
通知を送信する
チャンネル
を選択して、許可をクリック
-
作成された Webhook URL を
.env
ファイルに入力します。
SLACK_WEBHOOK_URL=Webhook URLを入力する
app
├── .env
├── config
│ └── credentials.json
└── src
├── googleAuth.js
└── mtgNotif.js
5. cron の設定
1.Terminal でcrontab -e
を実行して以下 cron を登録
45 8 * * 1-5 /bin/zsh -c 'source ~<ご自身のprojectPath>/NotifMTG/script.sh' >> ~/<ご自身のprojectPath>/NotifMTG/logs/cron.log 2>&1
6. 初回起動と認証
-
ターミナルで
node src/mtgNotif.js
を実行します。 -
ブラウザが開き、Google アカウントのログイン画面が表示されますので、ログインして、アプリのアクセスを許可してください。
-
認証が成功したら、ターミナルに
Authorization complete
と表示され、Slack に本日の MTG の通知が来ます。
最後に
実際に利用してみて、私にはもうなくてはならない物
になっています。
朝一に実施していた手作業のアラーム設定作業と、どこかでスケジュールのことを気にかけて、ぼんやりと頭のリソースを使っている状態から開放され、作業に集中する事ができるようになりました。
初期設定は色々と大変ですが、やってみる価値はあると思いますのでぜひ。
再起動後の自動実行や、追加登録されたスケジュールの通知も順次対応しようと思います.
また、内容に不備があれば、忌憚のないご意見をお願いします。
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
Discussion