🙆

GASで作る日程調整ツール

2022/02/07に公開

調整さんをはじめ、世の中いろいろな日程調整ツールがありますが、いまいちどれも使い勝手が微妙に感じたのでスプレッドシートを使用した日程調整ツールを作りました。

こんな感じでそれぞれが埋まっているところをスプレッドシート上でべた塗りし、全員が白いところを目視で探すだけのツールです。(ここまで書いて気付きましたが、それぐらいなら目視じゃなくて自動でチェックできそうですね)

ss

実装

GitHubで公開しています。導入方法はREADMEを読んでください。
https://github.com/Kasega0/Schega0

実は初版を作ったのは結構前なんですが、そのときはclaspを使用すればTypeScriptで実装できるという事を知らなかったので生JSで実装していました。そのときの名残がいまだにコードの端々に残っているので非常に汚いです。。。

要点

基本的には実装を見てもらえば十分だと思うので、ここでは自分がハマったところだけをいくつかまとめたいと思います。

import/exportが厄介

「gas import export」あたりで調べていただくとおそらくたくさんヒットすると思うのですが、実は一筋縄ではいかず結構厄介です。TypeScriptで実装したものをclaspでpushするときにGAS側でよしなにコードの変換を行ってくれているのですが、お世辞にも十分とは言えない状況です。

例えば、今回のプログラムでは次のように env.ts ファイルからトークンなどを読み込む予定でした。

env.ts
export const BOT_TOKEN = 'xoxb-'
main.ts
import { BOT_TOKEN } from './env'

本件に限らず通常のプログラムにおいてもよく見る光景だと思います。しかし、GASにおいてはgsファイルへと機械的に翻訳された結果、このimportが正常に行えずエラーが発生します。これについては公式も既に把握しており、大きく三種類の対応が提案されていますが

結局今回はclassを使用することで対応しましたが、なんとも言えない感じではあります。

env.ts
export class Config {
  public bot_token = () => 'xoxb-'
}
main.ts
import { Config } from './env'
const config = new Config()
config.bot_token()

3秒以内に応答

Slack appの要件としてスラッシュコマンドやショートカットからリクエストが飛んできたときに、3秒以内にレスポンスを返す必要があります。すけがおではシートの作成やセルの装飾など時間がかかりがちな処理を多数含んでいるので、

  1. リクエストを受ける
  2. シートの準備をする
  3. 完了後レスポンス

なんてことをしていたら絶対に間に合いません。そこで今回はリクエスト内容を一旦キャッシュ(参考:CacheService)に保存し、先にレスポンスを返したうえで、後からキャッシュ内容を時間駆動型の関数で呼び出してシートの作成を行っていました。流れとしては以下のようになります。

  1. リクエストを受ける
  2. リクエスト内容をキャッシュに保存
  3. レスポンス
  4. (時間駆動型の関数発火)
  5. キャッシュを読み出しシートを準備

実は初版のときも流れとしては同じだったのですが、時間駆動型の関数の呼び出し処理を少し変更しました。初版ではこの関数を毎分実行するように始めにイベントの設定をしていたのですが、サーバーにかかる負担を考えると良心が痛むのと、毎分実行されることでそれなりの頻度でエラーが発生しその通知が来るのが煩わしかったのとで次のように変更しました。

まず、リクエストを受け取った後に、そこから1分後にイベントが発火するようにTimeDrivenなトリガーを作成します。次に、そのトリガーのIDをキーにしてリクエスト内容をキャッシュに保存します。その後、設定したトリガーによって呼び出された関数が、引数のTrigger IDからキャッシュを読み出しシートの作成を行います。最終的な流れとしては次のようになります。

  1. リクエストを受ける
  2. 1分後に発火するトリガーを作成
  3. 設定したトリガーのIDをキーにリクエスト内容をキャッシュに保存
  4. レスポンス
  5. (時間駆動型の関数発火)
  6. Trigger IDをキーにしてキャッシュを読み出しシートを準備
slack.ts
// SlackからのPostリクエスト
function doPost(e: GoogleAppsScript.Events.DoPost) {
  /*
    (省略)
    リクエスト内容確認
  */
  
  // 60秒後に `sheetTrigger` 関数を実行するトリガーを作成
  const date = new Date()
  date.setTime(date.getTime() + 1000 * 60)
  const trigger = ScriptApp.newTrigger('sheetTrigger')
    .timeBased()
    .at(date)
    .create()

  // 作成したトリガーのIDをキーにリクエスト内容をキャッシュに保存
  const cache_key = trigger.getUniqueId()
  cache.put(cache_key, cache_data, 600)
  
  /*
    (省略)
    レスポンス送信
  */
}

// 上で設定したトリガーから呼び出される関数
function sheetTrigger(e: GoogleAppsScript.Events.TimeDriven) {
  // 引数のトリガー情報をもとにキャッシュを取得
  const cache = main.makeCache()
  const cache_key = e.triggerUid
  const cache_data = cache.get(cache_key)
  
  /*
    (省略)
    シート作成処理
  */
}

おわりに

またしてもGASの記事になってしまいました。別にGASが特別好きというわけではないのですが、タダで使えるものはありがたく使わせてもらっています。皆さんもよいGASライフを!

Discussion