Fusic Tech Blog

Fusicエンジニアによる技術ブログ

Bolt + TimeTree APIでSlackから予定を作成する
2024/03/25

Bolt + TimeTree APIでSlackから予定を作成する

プライベートでカレンダー共有アプリTimeTree をよく使っていて、デザインも良くとても気に入っています。

そんなTimeTreeがAPIを公開 したとのことで、Slackから予定の追加ができたら便利なのでは?と思い作ってみました。

開発環境

  • Node.js: 10.15.0
  • @slack/bolt: 1.2.0

Bolt

Slackが公式で出しているSlackアプリを作成するためのNode.jsフレームワークです。

どのようなものなのかサクッと試したい方は公式で入門ガイド がありますので、そこから試すことができます。

TimeTree API

TimeTreeでは最近APIが公開され、プログラムからTimeTreeへのアクセスが可能となりました。

でたばかりなのでまだまだできることは少ない(2019年9月8日現在)ですが、予定作成はできるようなので使ってみようと思います。

作成したSlackアプリ

予定作成デモ: /timetree create [カレンダー名] [予定タイトル] [開始日] [終了日]

timetree createコマンドを使って予定を作成するデモ

/timetree コマンドで自分のカレンダー一覧と予定作成ができるSlackコマンドを作ってみました。

  • カレンダー一覧: /timetree list
  • 予定作成: /timetree create [カレンダー名] [予定名] [開始日] [終了日]

完成したもののソースコードは公開しています。

https://github.com/7nohe/slack-bolt-timetree-app

ソースコード解説 (Bolt - Slackコマンドのリスニングと応答)

メインのコードだけ載せます。

index.js

const { getCalendarListMessage, createEvent } = require("./timetree");

// timetreeコマンドのリスニング
app.command("/timetree", async ({ command, ack, say }) => {
  // コマンドリクエストを確認
  ack();
  const [ commandName, calendarName, title, startAt, endAt ] = command.text.split(/(\s+)/);
  switch (commandName) {
    case "list": // カレンダー一覧コマンド
      const calendarList = await getCalendarListMessage();
      say(calendarList);
      break;
    case "create": // 予定作成コマンド
      const result = await createEvent(calendarName, title, startAt, endAt);
      say(result);
      break;
    default: // 該当なし
      say("そのコマンドには対応していません");
  }
});

commannd() メソッドでコマンドのリスニングを行なって、イベントを受け取ったことを ack() で確認します。

その後、 say() メソッドで応答して結果を返しています。

ソースコード解説 (TimeTree API - カレンダー一覧取得)

timetree.js

const fetch = require("node-fetch");

const TIMETREE_PERSONAL_TOKEN = process.env.TIMETREE_PERSONAL_TOKEN;

// TimeTree APIへのリクエスト
const fetchApi = (url, method = "GET", body = null) => {
  return fetch(url, {
    method,
    body,
    headers: {
      "Content-Type": "application/json",
      Accept: "application/vnd.timetree.v1+json",
      Authorization: `Bearer ${TIMETREE_PERSONAL_TOKEN}`
    }
  })
    .then(res => res.json())
    .catch(errorHandler);
};

// エラーハンドラー
const errorHandler = err => {
  console.error(err);
};

// カレンダー一覧GET
const getCalendars = () => {
  return fetchApi("https://timetreeapis.com/calendars").then(res => res.data);
};

// カレンダー一覧メッセージ取得
module.exports.getCalendarListMessage = () => {
  return getCalendars()
    .then(calendars => {
      if (calendars.length <= 0) return "カレンダーが見つかりませんでした";

      return calendars.reduce(
        (reducer, calendar) => reducer + `- ${calendar.attributes.name}\n`,
        "以下のカレンダーが見つかりました \n"
      );
    })
    .catch(errorHandler);
};

これはTimeTree APIを使ってカレンダー一覧データを取得する箇所です。

TimeTreeへの認証はパーソナルアクセストークンで行なっています。

https://timetreeapis.com/calendars に対してGETしてきて応答メッセージを作成しています。

ソースコード解説 (TimeTree API - 予定作成)

timetree.js

// イベントPOST
const postEvent = (calendarId, params) => {
  return fetchApi(
    `https://timetreeapis.com/calendars/${calendarId}/events`,
    "POST",
    params
  ).then(res => {
    console.log(res);
    return res;
  });
};

// カレンダーを名前で取得
const getCalendarByName = name => {
  return getCalendars().then(calendars =>
    calendars.find(calendar => calendar.attributes.name === name)
  );
};

// 日付バリデーション
const validateDate = date =>
  /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/.test(date);

// 予定を作成
module.exports.createEvent = (calendarName, title, startAt, endAt) => {
  return getCalendarByName(calendarName).then(calendar => {
    // 入力値バリデーション
    if (!calendar) return "指定したカレンダーが見つかりませんでした...";
    if (!title) return "予定タイトルを指定してください";
    if (!validateDate(startAt))
      return "正しいフォーマットで開始日をしてしてください";
    if (!validateDate(endAt))
      return "正しいフォーマットで終了日をしてしてください";

    // POSTパラメータ
    const params = {
      data: {
        attributes: {
          title: title,
          category: "schedule", // 予定
          all_day: true, // 終日
          start_at: `${startAt}T00:00:00.000Z`,
          end_at: `${endAt}T00:00:00.000Z`
        },
        relationships: {
          label: {
            data: {
              id: `${calendar.id},1`, // ラベル1
              type: "label"
            }
          }
        }
      }
    };
    return postEvent(calendar.id, JSON.stringify(params)).then(
      () => "予定を作成しました"
    );
  });
};

カレンダー一覧取得 → 入力されたカレンダー名にマッチするカレンダーIDを取得 → カレンダーIDを元に予定を作成

という流れになります。

今回は終日のみサポートしていますが、 all_day をfalseにすれば細かい時間での予定を作成することも可能です。

https://timetreeapis.com/calendars/:calendarId/events に対してPOSTすることでTimeTreeに予定を作成することができます。

指定できるパラメータは POST /calendars/:calendar_id/events で確認することができます。

Google App Engine(GAE)へのデプロイ

完成したBoltアプリをデプロイします。

ngrokを使ってローカルで動作確認する 方法もあるので、本番環境にデプロイする前にこちらで行うのもありです。

GCPアカウントを作成、gcloud CLIをインストール すれば簡単にできます。

app.yaml

runtime: nodejs10

includes:
  - secret.yaml

GAEへデプロイする設定として runtime を指定します。

secret.yaml

env_variables:
  SLACK_SIGNING_SECRET: "自分のSlack Sigining Secret"
  SLACK_BOT_TOKEN: "自分のSlack Bot User Oauth Access Token"
  TIMETREE_PERSONAL_TOKEN: "自分のTimeTree Personal Token"

さらに必要な環境変数を設定します。

SlackやTimeTreeの環境変数に関しては次に説明します。

設定が終えたら以下コマンドでデプロイできます。

$ gcloud app deploy

Slackアプリの設定

Boltアプリをデプロイできたら、次にSlackアプリの設定を行います。

やることとしては以下になります。

  • Slack Appの作成
  • Basic InformationからSigning Secretの取得
  • Bot Userの追加
  • Install AppからBot User Oauth Access Tokenの取得
  • Slashコマンドの追加(この時にGAEにデプロイしたリクエストURLを設定)

詳しくは

でわかりやすく解説されています。

TimeTree Personal Access Tokenの取得

https://timetreeapp.com/personal_access_tokens からTimeTree APIへアクセスするためのPersonal Access Tokenを作成します。

取得したSlackとTimeTreeの各トークン情報などは環境変数として設定してください。

まとめ

Boltを利用することで簡単にSlackアプリを作成することができました。

今後TimeTree APIも予定一覧の取得などもできるようになれば、Slack上で予定の通知、確認などができると思うので期待しています!

Daiki Urata

Daiki Urata

フロントエンド/モバイルアプリなどを主に開発しています。