📝

タスク管理とタスクをリマインドをするSlackアプリを作ってみた

2024/06/28に公開

Slackアプリとは

Slackアプリとは、Slack上で機能を追加するために使われるアプリケーションです。
SlackにはGUIで操作できることのほとんどをAPIでも操作可能です。
様々なサービスからSlackアプリがリリースされています。
おすすめのアプリをいくつかピックアップしてみます。

  • Github

    • Githubでのメンション、PRのレビュワーにアサイン通知、放置しているPRレビューなどを通知してくれます。
  • Google Calendar

    • Googleカレンダーに登録されているスケジュールをSlackでも確認できます。リマインド機能もついています。
  • Polly

    • Slackメッセージ内で気軽に投票機能が使えます。イベントや飲み会に参加できる人に参加可能日のアンケートを気軽に取ることができます。

Slackタスク管理アプリの概要

「リマインちゃん」 というSlack上で完結するタスク管理アプリを内製アプリとして作成しました。
Slack上の任意のメッセージのメニューボタンからリマインちゃんを選択すると自動でタスク登録されます。
社内アプリとして作成したためソースコード全ては出せませんが、いつかパブリックにしても良い形で公開したい気持ち。


また、タスクのタイトルと期限とはAI(GPT-4o)で自動判定しています。
タスク登録後に使える機能は以下の通りです

  • 期限の修正機能
  • 未完了者の一覧表示機能(ボタンを押した人のみ表示)
  • 未完了者へメンションを飛ばす機能
  • 未完了者へリマインダーを送る機能(バッチ処理)
  • Slackアプリホームから自分自身のタスクを追跡する機能
  • チャンネル内でアプリに対してとメンションをすると、そのチャンネルで発生したタスク一覧を表示する機能

使用技術

  • SlackApp
  • Python
  • Slack Bolt(for Python)
  • MySQL 8.4 LTS
  • ORM SQLAlchemy
  • Docker
  • Azure OpenAI Service(Assistants APIを使用)

システム構成図

  • ハンドラー用アプリ

    • Slackからのリクエストを受け止めるアプリ
    • WebSocket通信でSlackと通信
  • バッチ用アプリ

    • タスク未完了者に定期的にリマインドするためのアプリ
    • nginxでリバースプロキシしている
    • 同じConoHaインスタンス内で稼働しているRundeckからバッチアプリのエンドポイントにGETでリクエスト

ER図

タスク登録する際に、タスク元メッセージのts(タイムスタンプ)を取得しDBに記録しています。
また、タスクそのものの完了状況(task_list.is_closed)を設定。
task_listのtask_idと外部キー制約で紐づけたuser_task_statusにユーザごとのタスク終了状況を保持。
specfic_channnelには、特定のチャンネルではタスク登録させないなどの制御を入れたい場合に使用するテーブル。
ai_promptは後述のAzure OpenAI Serviceで活用するプロンプトを保持しています。

活用した機能、特筆すべき点など

Azure OpenAI Serviceの活用

タスクを登録する際に期限やタイトルなどを手入力することなく、ワンクリックで登録するアプリを作ることがコンセプトのひとつでした。
それを叶えるために活用したのがAzure OpenAI Service(AOAI)です。
将来の機能拡充に備えてAssistantsAPIを使用しています。

プロンプト

文章を読み取り、JSON形式で要約してください。
    due_dateはyyyy-MM-dd hh:mm形式でお願いします。
    時間指定されていない場合は一律午後19:00として設定してください。
    明日や明後日という表現の場合は今日の日付を基準にしてください。

    例
    {
        "title": "PCの返却期限について",
        "due_date": "2024-06-06",
    }'
今日の日付はmm月dd日です

今日の日付はmm月dd日ですの部分はPythonアプリ側から動的にコードを差し込むようにしています。
また、プロンプトはPythonにハードコーディングするのではなく、DBに保管することでプロンプトを簡単に変更できるようにしています。

サンプルコード

from openai import AzureOpenAI

from service.db_service import DBService


class AzureOpenService:
    def __init__(self):
        self.openai_client = AzureOpenAI(
            api_key=os.getenv("AZURE_OPENAI_API_KEY"),
            api_version="2024-05-01-preview",
            azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
        )
        self.db_service = DBService()

    def extract_title_and_duedate(self, message: str) -> str:
        """Promptを元にcompletionsを生成する"""

        if "【TEST】" in message:
            # 開発時はサンプルデータを使用する
            return {
                "title": "【TEST】PCの返却期限について",
                "due_date": "2024-06-06 10:00",
            }

        # Aliasが特定の値に一致するエントリを検索(DBの中にプロンプトを書いています)
        ai_prompts = self.db_service.fetch_ai_prompt_by_alias("CREATE_TASK")

        response = self.openai_client.chat.completions.create(
            model=os.getenv("AZURE_OPENAI_MODEL"),
            messages=[
                {"role": "system", "content": f"指示: {ai_prompts[0].prompt} 今日の日付: {datetime.now()}です"},
                {"role": "user", "content": message},
            ],
        )
        message_info = response.choices[0].message.content[
            response.choices[0].message.content.find("{") : response.choices[0].message.content.rfind("}") + 1
        ]

        return json.loads(message_info)

Slackメッセージの 「あなただけに表示しています」機能

タスクの未対応者一覧を確認する機能を実装しました。
画像のように未対応者を表示させたかったのですが、毎度チャンネル参加者が見える形で投稿されるとノイズとなるため、
chat_postEphemeralを利用しました。これを利用することで「あなただけに表示しています」という形で投稿されます。

def chat_post_ephemeral(self, channel, user, thread_ts, text, blocks=None):
    """特定人物のみ見れるメッセージを送信する"""
    return self.app.client.chat_postEphemeral(
        channel=channel,
        user=user,
        thread_ts=thread_ts,
        text=text,
        blocks=blocks,
    )

WebSocketでの通信

Slackアプリでは、エンドポイントをこちらで立てて利用するモードと
WebSocketを使ったモードの2つがあります。
どちらのモードにもそれぞれメリット・デメリットがありますが、WebSocketのメリットは

  • サーバ側でインバウンド通信のポート開放をしなくて良い
  • 証明書などの設定が不要

などが挙げられます。今回、Dockerを使用しているためdocker-compose.ymlでWebSocket用のポートを開放していますが、
ConoHa(Ver.2)のセキュリティグループの設定でインバウンド通信は許可しない設定にしています。

services:
  slack-task-handler-app:
    container_name: slack_task_handler_app
    build:
      context: .
      dockerfile: Dockerfile.slack_handler
    depends_on:
      - slack-task-app-mysqld
    environment:
      TZ: Asia/Tokyo
    ports:
      - "444:443" # ここ
    networks:
      - slack-task-app-network
    restart: always
    volumes:
      - ./app/logs:/usr/src/app/logs

※本来444はSNPPのためのポートらしいですが、ホストOSの443を他のサービスで使用しているため444に設定

参考にしたサイト

  • Bolt for Python

    • Boltの公式ガイドです。BoltでSlackアプリを開発する場合は必読です。
  • Slack Block Kit Builder

    • Slack公式のブロックの作成を支援してくれるツールです。
      Slackメッセージや、モーダル、アプリホームは、Blockの中身を組み立てることで表示しています。
  • Slack API

    • SlackAPIのすべてがここに。
GitHubで編集を提案
GMOメディアテックブログ

Discussion