📑

Slackで特定のメンション宛の返信のないメッセージにリマインドする

2024/07/16に公開

はじめに

こんにちは。SKIYAKIのエンジニアやってる家(@ie_webengineer)と申します。
ファンクラブを開設できる『Bitfan』の開発をメインに担当しております。

やりたいこと

  1. slackAPIを使えるようにする
  2. 特定のチャンネルの現在日時の25時間前〜1時間前に投稿されたメッセージ履歴を取得
  3. メッセージが特定のメンションを含むか確認
  4. メッセージに返信が1件もない or リプライがリマインドメッセージしかないか確認
  5. 上記条件で絞り込まれたメッセージに対して元々のメンションをつけてリマインドメッセージをする
  6. GASで定期実行する

BitfanはRubyを使っているのでそれでやろうかと思いましたが、
色々面倒なんで今回はGAS(Google Apps Script)を使って
直接slackAPIを実行しようと思います。

作ろうと思ったきっかけ

日々、slackでディレクターからBitfanの仕様に関しての質問や対応依頼などが来るため回答を返すのですが、
急ぎでない場合、今している対応を終えてから回答しようってなる時もあります。

たまに質問に対して回答が1日ないことがあったりするということが発生して、
営業チームに催促のメッセージをもらうことがありました。

エンジニアチームが完全に回答を忘れてしまっている時に
営業チームに催促させるのも申し訳ない。。。
ただエンジニアチームもすぐに回答できる状況になく忘れることもある。。。

それなら誰も気を使う必要のないようにシステムでリマインドしようとなりました。
ネット上に情報転がってないかなと思いきややりたいことをしているものはなかった...
じゃあ作るしかない...

実装してみる

slackAPIを使えるようにする

今回slackAPIの設定については良き記事があったので省きます。

以下記事の「アプリを作成する」〜「アプリをチャンネルに追加する」までを対応する
https://zenn.dev/kou_pg_0131/articles/slack-api-post-message

Scopeは今回チャンネルのメッセージ取得とチャンネル投稿のため「channels:history」と「chat:write」を設定します。

特定のチャンネルの現在日時の25時間前〜1時間前に投稿されたメッセージ履歴を取得

※ある程度絞った方が良いということでこの日時設定にしただけです

const token = 'slackのBotUserOAuthToken'
const channelId = 'チャンネルID'
const headers = {
  'Authorization': 'Bearer ' + token,
  'Content-Type': 'application/x-www-form-urlencoded'
};
const now = Date.now();
// 現在時刻から1時間前のUNIX時間を計算
const oneHourAgo = (now / 1000) - 3600;
// 現在時刻から25時間前のUNIX時間を計算
const twentyFiveHourAgo = oneHourAgo - 24 * 3600;

// チャンネルのメッセージ履歴を取得
const historyResponse = UrlFetchApp.fetch('https://slack.com/api/conversations.history?channel=' + channelId + '&oldest=' + twentyFiveHourAgo + '&latest=' + oneHourAgo, {
  'headers': headers
});
const historyData = JSON.parse(historyResponse.getContentText());
const messages = historyData.messages;

メッセージが特定のメンションを含む && メッセージに返信が1件もない or リプライがリマインドメッセージしかないか確認

let filteredMessages = [];

const mentions = ['<!subteam^ユーザーグループID|@ユーザーグループ名>'];
for (let j = 0; j < messages.length; j++) {
  const message = messages[j];

  // 特定のメンションを含むか確認
  const containsMention = mentions.some(function(mention) {
    return message.text && message.text.includes(mention);
  });

  if (containsMention) {
    // スレッドのリプライを取得
    const repliesResponse = UrlFetchApp.fetch('https://slack.com/api/conversations.replies?channel=' + channelId + '&ts=' + message.ts, {
      'headers': headers
    });

    const repliesData = JSON.parse(repliesResponse.getContentText());
    // 返信がない場合
    const replys = repliesData.messages

    if (replys.length === 1) { 
      filteredMessages.push(message);
    } else {
      // 最初のメッセージはリプライではないため除外
      replys.shift();
      // リマインダーメッセージしかリプライがない場合は再度リマインドの対象にする
      const IsNothingAnswer = replys.every(function(reply) {
        return reply.text.includes('問い合わせを受けてから1時間以上経過しています');
      });
      if (IsNothingAnswer) {
        filteredMessages.push(message);
      }
    }
  }
}

絞り込まれたメッセージに対して元々のメンションをつけてリマインドメッセージをする

// 絞り込まれたメッセージに対してリプライを投稿
filteredMessages.forEach(function(message) {
  // メッセージからユーザーグループのメンションを1件抽出
  const userGroupIDRegex = /<!subteam\^(.*?)\|@.*?>/;
  const match = userGroupIDRegex.exec(message.text)
  const replyMention = match[0]
  const replyText = replyMention + '\n問い合わせを受けてから1時間以上経過しています。\n対応お願いいたします。'
  const params = {
    'method': 'post',
    'contentType': 'application/x-www-form-urlencoded',
    'payload': {
      'token': token,
      'channel': channelId,
      'text': replyText,
      'thread_ts': message.ts
    }
  };

  UrlFetchApp.fetch('https://slack.com/api/chat.postMessage', params);
});

GASで定期実行する

GASの設定も記事があったので省きます。

https://blog.synnex.co.jp/google/gas-trigger/

補足

・ユーザーグループIDの確認方法で参考にした記事
https://tools.bigwave.biz/notes/blog/posts/ruby_slack_message_to_user_group

結果

内容はお見せできないのですが、1時間おきにスクリプトを実行すると
投稿から1時間以上経過している質問に対してこんな感じでリマインドしてくれました。

まとめ

このリマインダーを作ったは良いけど
結果的にリマインドせずともエンジニアチームが質問に対して
すぐ反応できればこのスクリプトは不要です。

ただ優先順位はあるし、日々の開発業務&ミーティングもあるので、
すぐに回答できない理由もあるため
うまい具合に今回作成したスクリプトが機能してくれれば嬉しいなと願います。

SKIYAKI Tech Blog

Discussion