10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Slackで自分にきたメンションを、自動でタスク化してNotionに登録するアプリの作成した話

Last updated at Posted at 2025-01-19

はじめに

忙しいor面倒臭すぎて自分に振られた作業を、手動でタスク化出来ない。
Notionとかで管理したいけど、面倒すぎる。
そうだ自動化しよう。


まずは完成した時の画像をどうぞ

以下は、実際にアプリが動作したときのスクリーンショットです。

1. Slackでの依頼メンション

Slackで自分宛に送信された依頼メッセージ

Slackでの依頼メンション

2. Notionに登録されたタスク

上記メンションが解析され、Notionにタスクとして登録

Notionに登録されたタスク

以下作業手順

実現したいこと

  1. Slackメンションをトリガー:
    • 自分宛のメンション(特定のキーワードを含む)を受信。
    • キーワード例: "対応", "お願い", "緊急", "依頼"。
  2. Gemini-Proでタスク解析:
    • メンション内容を解析し、タスク名・説明・期限日を抽出。
  3. Notionにタスクを登録:
    • タスクデータをNotionデータベースに登録。

使用技術

  • TypeScript: プロジェクト全体で使用。
  • Next.js: サーバーレスAPIを構築。
  • Google Gemini-Pro API: 自然言語処理でメンション内容を解析(gemini-1.5-flashモデル)。
  • Notion API: タスクをデータベースに登録。
  • Slack API: メンションイベントを取得。

環境構築

必要なもの

  • Slackワークスペース
  • Notionアカウント
  • Google Gemini-Pro APIキー

コード解説

1. Slackイベント処理(events.ts

Slackメンションを受け取り、タスクを作成します。

import { NextApiRequest, NextApiResponse } from "next";
import { analyzeTask } from "../../../utils/gemini";
import { addTaskToNotion } from "../../../utils/notion";

const processedMentions: Set<string> = new Set();
const taskKeywords = process.env.TASK_KEYWORDS?.split(",") || [];
const targetUserId = process.env.TARGET_USER_ID || "";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.body.type === "url_verification") {
    return res.status(200).json({ challenge: req.body.challenge });
  }

  try {
    const event = req.body.event;
    const mentionId = event.client_msg_id || event.event_ts;

    if (!mentionId || processedMentions.has(mentionId)) return res.status(200);

    if (!event.text.includes(`<@${targetUserId}>`) || 
        !taskKeywords.some((keyword) => event.text.includes(keyword))) {
      return res.status(200).send("No task-related keywords found.");
    }

    const { taskName, dueDate, remarks } = await analyzeTask(event.text);

    const properties = {
      タスク名: { title: [{ text: { content: taskName } }] },
      備考: { rich_text: [{ text: { content: remarks || "" } }] },
      期限日: { date: { start: dueDate || "" } },
    };

    await addTaskToNotion(properties);
    processedMentions.add(mentionId);

    res.status(200).send("タスクが正常に登録されました。");
  } catch (error) {
    res.status(500).send("内部サーバーエラー");
  }
}

2. gemini-1.5-flashでタスク解析(gemini.ts

Google gemini-1.5-flashを使用してメンション内容を解析し、タスクデータを生成します。

import { GoogleGenerativeAI } from "@google/generative-ai";
import { generatePrompt } from "./prompt";

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);

export async function analyzeTask(input: string) {
  const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
  const prompt = generatePrompt(input);
  const result = await model.generateContent(prompt);

  let taskData = JSON.parse(result.response.text().replace(/`/g, ""));
  taskData.dueDate = taskData.期限日?.replace(/(\d{4})\/(\d{2})\/(\d{2})/, "$1-$2-$3");
  
    return {
      taskName: taskData["タスク名"] || "未定義タスク", // タスク名がundefinedならデフォルト値を設定
      dueDate: taskDueDate || "2025-01-01", // 期限日がnullならデフォルト日付を設定
      remarks: taskData["備考"] || "説明なし", // 備考がundefinedならデフォルト値を設定
    };
}

3. Notion API連携(notion.ts

タスクデータをNotionデータベースに登録します。

import { Client } from "@notionhq/client";

const notion = new Client({ auth: process.env.NOTION_TOKEN! });

export async function addTaskToNotion(properties) {
  return await notion.pages.create({
    parent: { database_id: process.env.NOTION_DATABASE_ID! },
    properties,
  });
}

4. プロンプト生成(prompt.ts

Gemini-Pro用のプロンプトを生成します。

export function generatePrompt(text: string): string {
  return `
      以下のSlackメンションを解析し、指定されたNotionデータベースフォーマットに合うタスクを生成してください。
      
      Slackメンション:
      "${text}"
      
      Notionデータベースのフォーマット:
      - タスク名: タスクの主題
      - 備考: 詳細説明
      - 期限日: YYYY-MM-DD形式
      
      出力例:
      {
        "タスク名": "新しいプロジェクトの企画書を作成する",
        "備考": "プロジェクトの詳細はSlackスレッドを参照",
        "期限日": "2025-01-30"
      }
      
      解析対象:
      - メンションに期限が含まれていない場合、"期限日"は空欄にしてください。
      - 備考が指定されていない場合は空欄にしてください。
      - 必要に応じて備考を補足してください。
    `;
}


難しかった部分

  1. Slackメンションの無限ループ:

    • 同じメンションが何度も処理されてしまい、Notionにタスクが無限に登録される問題が発生。
    • 解決策: Slackイベントのclient_msg_idevent_tsを利用して処理済みのメンションをSetで管理。
  2. JSONのパースエラー:

    • Geminiのレスポンスに含まれる余分なバッククォートやフォーマットミスでエラーが発生。
    • 解決策: レスポンスの前後の整形処理を追加。

工夫した部分

  • イベントIDで重複処理を防止:
    • Slackイベントごとに一意のIDを使い、処理済みかを追跡。
  • キーワードフィルタリング:
    • 特定のキーワード(例: "対応", "お願い", "緊急", "依頼")を使い、タスク化対象を絞り込む。
  • 柔軟なJSONパース処理:
    • Geminiのレスポンスフォーマットに合わせて柔軟にデータを整形するロジックを実装。

参考にした記事

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?