Slackに独自のリマインダー機能を作った話

# はじめに
[前回](https://qiita.com/ORESAMAChanMg/items/2a04967d19eed0fa25d8)に引き続き、Slackを活用するために工夫した点について、記事を書いていきます。
今回はSlackリマインダーでは少し物足りない部分をGoogleスプレッドシートとGASを駆使して少し自動化したというお話です。

## 前提知識
Googleアカウント、スプレッドシート、AppsScriptについては、ざっくりしか書いてないです。

## 本記事で解決する問題の概要
本記事では、Slackのリマインダー機能における課題を解決するための方法を提案します。Slackのリマインダーには設定の複雑さやカスタマイズの限界、リマインダーの管理の難しさなどの課題がありますが、本記事ではそれらの課題を解決するための手法を紹介します。
具体的には、GoogleスプレッドシートとApps Scriptを活用して独自のリマインダー機能を作成する方法を解説します。これにより、Slackのリマインダーが持つ制約を乗り越え、効率的なリマインダー管理が可能となります。


# Slackリマインダーの課題

- Slackを使っていて、気になるところがあったりしませんか？例えば...
    - **設定の複雑さ**: Slackのリマインダー設定は基本的にシンプルですが、複数の条件を組み合わせてリマインダーを設定する場合や、繰り返しリマインダーを設定する場合には、操作が煩雑に感じられることがあります。

    - **カスタマイズの限界**: Slackのリマインダー機能は、特定の日時にメッセージを送るという基本的な機能しか提供していません。曜日や特定の条件に応じてリマインダーを設定することが難しいため、柔軟なカスタマイズができないという課題があります。

    - **リマインダーの管理**: 多くのリマインダーを設定する場合、それらを一元的に管理する手段が不足しているため、設定したリマインダーを見失ってしまうことがあります。また、リマインダーを編集したり削除したりする際も、管理が煩雑になることがあります。

    - **リマインダーの使い勝手**: Slackのリマインダーは特定のメッセージに対してのみ設定できるため、タスク管理や定型業務にはあまり適していません。また、リマインダーを設定する際には、一度メッセージを投稿してからリマインダーを設定する必要があるため、手間がかかると感じるユーザーもいます。

    - **通知の遅延**: Slackのリマインダーは通知が遅れることがあり、リアルタイムなタスク管理には向いていないと感じるユーザーもいます。特に、スケジュールの厳密さが求められる場面で問題となることがあります。
　
- 他、実体験で得た課題感（個人的な感想）
    - 自社の営業カレンダーに適した設定ができない（例：年末年始、GW、お盆など）
    - 事前に予定を仕込んだリマインダーの設定を都度メッセージ投げては訂正して（結構間違える...）といった作業になるのが煩わしい
    - 曜日毎に定型の業務内容を記載したい
    - なんなら隔週とかも設定したい

# Slackリマインダーのメリット・デメリット
ここまでデメリット（課題感）ばかりだったので、メリットを探る
- メリットの解説
    - **シンプルな操作**: Slackリマインダーは直感的な操作で設定できるため、初心者でも簡単に利用することができます。
    - **チームとの共有**: Slackリマインダーはチーム全体と共有できるため、重要なタスクやイベントの共有がスムーズに行えます。
    - **Slackとの連携**: SlackリマインダーはSlackと密接に連携しており、チャット内での作業効率を向上させることができます。

Slackリマインダーは、シンプルで使いやすい機能でありながら、チームでの作業やコミュニケーションを効率化する上で非常に役立つ機能です。
　
（実はここまでほぼ生成AIに書かせています。確かに言われてみれば、そうかもしれないが業務によっては、もう少しここをどうにかしたいなって思うシーンが結構あります。Slackが業務に密接に絡みだした結果、この独自リマインダー作成に繋がった感じです）

## 独自リマインダーが投稿してくるメッセージはこんなもの
※実務で使ってるものです
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/79cb9810-88c1-cb9b-626f-afa6c65029b8.png">
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/b62ac3fc-55f3-8de1-9da0-b4d549b68bfe.png">
<img width="390" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/3d8b1194-2808-6c34-d8e5-cbf31b64f6fd.png">


# 独自リマインダー（Slackアプリ）の作成の流れ
※ワークスペース内の設定に関わるため、権限が必要な作業です
1. Slackに独自のアプリを追加します
    1. https://api.slack.com/apps/ にアクセス
    1. あとは・・・良い記事があるので、こちらを参考に
        1. https://zenn.dev/kou_pg_0131/articles/slack-api-post-message
　
1. チャンネル側のインテグレーション設定でアプリを追加する（Token使わないから、これいらないかも？..）
    1. Slackのチャンネル名をクリックすると設定ウィンドウ？が出てきます
    1. インテグレーションタブを選択
    1. アプリを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/25f228d3-82bd-51ae-a340-b6c079fb5150.png)
    1. 先ほど追加したアプリを選択する
    1. チャンネルに通知が飛びます（何も知らないチャンネルメンバーがドキッとする瞬間ｗ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/6248bf9a-eb31-839d-7de3-ba7cb11bec87.png)

　
1. Incoming Webhooksの設定
    1. https://api.slack.com/apps/ にアクセス
    1. AppNameから該当するアプリを選択
    1. Add features and functionalityを選択
    1. Incoming Webhooksを選択
    1. Add New Webhook to Workspaceを選択
    1. 投稿するチャンネルを選択して、許可するを押下
    1. Webhook URLに１つ追加されているので、Copyを押下（あとでGASスクリプトに埋め込む）




# 独自にリマインダーを作る前に準備すること（Googleドキュメントまわり）
- GoogleスプレッドシートとAppScriptを使った方法の概要
    - もしなければ、Googleアカウント作ってください
    - Googleスプレッドシートを準備してください
        - 今回は以下のような列をもつ表を準備しました

| 列名 | 説明 |
|:-|:-|
|No   |ただの連番、管理用です   |
|種類   |今回は"会議体"or"タスク" を記載  |
|内容   | 「10時からスタンダップミーティングです」<br/>といったリマインダーテキスト  |
|発火条件   |   |
|　1:曜日   |日0、月1～土6   |
|　2:隔週   |-1:毎週/0:偶数週/1:奇数週   |

- スプレッドシートはこんな感じにしています（実際の業務で使っているもの）
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2149410/d1552882-855d-e9e8-f513-606c2b97ab2d.png)
    - 内容のところはマークダウン形式で書くと、文字装飾もできます
        - https://api.slack.com/reference/surfaces/formatting
        - 余談：Slackのマークダウンは"mrkdwn"と呼ばれるちょっと別モノ言語
　
- スプレッドシートを開き、拡張機能⇒AppsScriptを選択してください
    - gsファイルを２つ準備します
    - SetTrigger.gs
        - AppsScriptトリガーに設定するもの
        - こやつが所定の時刻に発火し、次のスクリプトをコールします
        - なんでこんなものを準備するかは後述
    - AutoPostMessage.gs
        - Slack Tokenを用いたメッセージ投稿スクリプト

これで、準備は完了です


# AppScriptのコード紹介
- **発火時刻の制御について**
    - AppsScriptのトリガーで毎日いろいろ動かすには実はあまり細やかな設定ができない
    - そのため、日付毎に0時～1時にAppsScript（SetTrigger.gsのmainメソッド）を起動して、スクリプト内から指定時刻に動き出すトリガーを自前で作ります（ScriptApp.newTrigger()）
　
- **メッセージ投稿の制御について**
    - 言わずもがな、Googleスプレッドシートから投稿内容を取得しています
        - こうすることでリマインダーの設定をスプシで管理できるようになって、Slackリマインダーの課題がひとつ解決します
    - 独自のマークダウン形式（mrkdwnと言う）にて投稿メッセージにスタイルを適用
        - https://api.slack.com/reference/surfaces/formatting
    - 投稿メッセージのデザイン確認にはBlock Kit Builderを利用
        - https://app.slack.com/block-kit-builder/
    - 今回の取り組みでは曜日や隔週だけしか指定できません
        - が、日付や時刻指定、年間スケジュールなんかを足してみるのもいいかもです
        - 複数のチャンネルに渡る投稿設定管理なんかもスクリプトを駆使すれば実現可能
    - 土日は基本的に休みなのでスキップさせました
    - 祝祭日はGoogleカレンダーから取得します
        - Googleカレンダーを利用した祝日情報の取得方法（たぶん見た方が早いので、コードをあとで示します）
    - 隔週はちょっと細工が必要です（ここは理解不能なので、コードをあとで示します。悩みたくない人はコピペしてくださいｗ）

## コード紹介：トリガー設定まわり
``` javascript
//AppsScript側トリガーにより、毎日0時～1時に以下のmainが発火します
function main() {
  var dtm = new Date();
  //9:10に発火するよう設定（秒数はズレます）
  dtm.setHours(9);
  dtm.setMinutes(10);
  //子トリガーを追加する
  ScriptApp.newTrigger('SpecifiedTimeTrigger').timeBased().at(dtm).create();
}

//指定時刻に発火させる子トリガーの処理
function SpecifiedTimeTrigger() {
  //
  //ここに指定時刻に動かす関数を記述する（AutoPostMessage.gs側の関数）
  //

  //以降、子トリガーの後始末
  const triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if(triggers[i].getHandlerFunction()==='SpecifiedTimeTrigger') {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}
```

## コード紹介：投稿メッセージの編集まわり
殴り書きで作ったものなので、全文掲載は控えます。。。
必要なところだけ搔い摘んで張り付けておきますね
- SetTriggerから呼び出されるmainメソッドへの記述
``` javascript
  const url = "｛ここに Incoming Webhooks のURLが必要｝";
  //本当はこの位置に発火条件判断を加えます（条件未達なら早期リターン）。割愛
  var payload = getPayload();//オリジナル関数です、さっき紹介したBlock Kit Builderで作るようなJSONテキストを返す関数を準備します。後述
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  UrlFetchApp.fetch(url, options);
```

- payload編集関数（Slack投稿用のJSON文字列を返す）
``` javascript
function getPayload() {
    var blockWork = new Array();
    var ret = {"text": "任意のテキスト"};
    var blockWork = new Array();
    var idx = 0;
    var today = new Date();
    var dtNow = getFmtDate(today, 'yyyy-MM-dd HH:mm:ss (E)')  //タイムスタンプ表示用
    //ヘッダー部
    blockWork[idx++] = {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "｛適当に日付や曜日のテキストを書く｝"
      }
    }
    //メッセージのサブタイトル
    blockWork[idx++] = {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",  //ここはミソ。スプレッドシートにmrkdwn形式で書いておけば装飾可
          "text": dtNow + " <!here>"
        }
      ]
    }
    //区切り線
    blockWork[idx++] = {
      "type": "divider"
    }
    //会議体/タスクのセクション分け表現
    blockWork[idx++] = {
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": "会議体"
        }
    }
    //実際のメッセージ部分（ここはプログラム的にはスプレッドシートの行数分ループさせますが割愛）
    blockWork[idx++] = {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "｛スプレッドシートの該当行のテキスト｝"
      },
    }
    ret.blocks = blockWork;
    return JSON.stringify(ret);//JSON文字列として返す、おまじない
}
```

## コード紹介：週番号の取得方法（隔週算出用）
``` javascript
const thursday = new Date(Math.ceil((date.getTime() - 259200000) / 604800000) * 604800000);
const firstDayOfYear = new Date(thursday.getFullYear(), 0, 1);
const weekOfYear = Math.floor((thursday.getTime() - firstDayOfYear.getTime()) / 604800000) + 1;
```

（参考URL）https://qiita.com/proudust/items/a85b386b9899356b4c9c

## コード紹介：祝祭日の取得方法
``` javascript
var today = new Date();
var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
let calendar = CalendarApp.getCalendarById(calendarId);

let dayEvents = calendar.getEventsForDay(today);
if(dayEvents.length > 0){
    return true;//イベントがある＝祝祭日ってこと
}
```
    
# まとめ

- 本記事で提案する方法の利点のまとめ（を書かせてみた、**生成AIに**）
    - **柔軟なリマインダー設定**: Google Apps Scriptを使うことで、Slackのリマインダー機能では実現困難な、曜日や特定の条件に応じたリマインダーの設定が可能となります。
    - **集中的な管理**: Googleスプレッドシートを利用することで、多くのリマインダーを一元的に管理できます。リマインダーの追加や変更が容易に行えるため、効率的なタスク管理が可能です。
    - **通知の正確性**: Google Apps Scriptのトリガー機能を活用することで、リマインダーの通知が正確に行われます。特定の条件や時間に合わせてリマインダーを送信できるため、タスクの漏れを防ぐことができます。
    - **拡張性の高さ**: Google Apps ScriptはGoogleの各種サービスと連携が可能であり、様々な機能の拡張が可能です。これにより、さまざまなニーズに応じたリマインダー機能のカスタマイズが可能となります。
　
- 今後の展望や発展の可能性について（を書かせてみた、**生成AIに**）

    - **機能の拡充**: 現在は特定の条件や時間に合わせてリマインダーを送信する機能を提供していますが、さらに高度な条件やカスタマイズが可能となる機能の追加が期待されます。例えば、特定のユーザーにのみリマインダーを送信するなど、細かい設定が可能となると、さらに多様な用途に活用できるでしょう。
    
    - **通知方法の拡張**: 現在はSlack内での通知が主ですが、他のチャットツールやメールなど、さまざまな方法での通知ができるようになると、利用シーンがさらに広がるでしょう。🤔
    
    - **外部サービスとの連携**: 今後は、SlackやGoogleカレンダー以外の外部サービスとの連携が強化される可能性があります。例えば、タスク管理ツールやカレンダーアプリとの連携により、よりスムーズな業務効率化が実現されるでしょう。
    
    - **ユーザビリティの向上**: より直感的で使いやすいインターフェースや操作性の向上が期待されます。特に初心者や非技術者にとって、より簡単に利用できるような改善が求められます。
    
    - **セキュリティの強化**: リマインダーに含まれる情報のセキュリティ面での強化が重要です。情報漏洩や不正アクセスへの対策が必要です。
    
    - **データ分析機能の拡充**: ユーザーのリマインダー利用履歴や傾向を分析し、より適切なリマインダー提案を行う機能が追加される可能性があります。
    
    - **APIの提供**: 開発者向けにAPIを提供することで、外部サービスやアプリケーションとの連携が容易になり、より幅広い用途での活用が可能となります。
    
    - **クロスプラットフォーム対応**: 現在はSlackに特化していますが、他のチャットプラットフォームやタスク管理ツールとも連携できるようにすることで、より広範なユーザーに利用される可能性があります。
　
 - 独自リマインダーを運用してみて（個人的な感想を書いてみた）
    - Slackのチャンネルのメッセージの流れに、少し節目を持たせることができ、止めどなく流れていくメッセージがかなり見やすくなった印象を受けます
        - 毎朝9:10に「本日の予定」というスレを立てて、1日が始まる感じです
        - そこに業務に必要な情報が出ているのでインデックス的にも利用できています
    - リマインダーの管理がしやすくなった
        - ちょっとした予定の変更などの記述をSlackのメッセージ（リマインダー編集）ではなく、スプレッドシートへの書き込みで実現できるので、打ち間違いなどでうっかりメッセージが投稿されるといった（チャンネルメンバーの目に止まるので、ノイズになりやすかった）事故がなくなりました
    - ただ、Googleサービスに頼った構成なので、クラウドサービス側にトラブルがあると、リマインダーが動かないことがあります（どのクラウドサービス使っていてもこれは避けられない）
        - 1年ほど運用していて1回だけ事象が起きました

# あとがき
社内では自称「生成AIアンバサダー」だと言いふらしながら、生成AIを何かしらに業務に取り込もうと日頃頑張っていて、この記事の書き始めにも、ちょっと頼ってみようかなと思い立って走り出しました。
で、生成AIと会話しつつ、数時間（それはそれでかけすぎって言われるかもですが）で、それっぽいものが仕上がりました。まだまだ使い方が甘いと思うので、常々横に置いて使い倒したいなというところ。

# 前回までの記事

https://qiita.com/ORESAMAChanMg/items/2a04967d19eed0fa25d8

https://qiita.com/ORESAMAChanMg/items/948048c787fae85716c9