はじめに
忙しいor面倒臭すぎて自分に振られた作業を、手動でタスク化出来ない。
Notionとかで管理したいけど、面倒すぎる。
そうだ自動化しよう。
まずは完成した時の画像をどうぞ
以下は、実際にアプリが動作したときのスクリーンショットです。
1. Slackでの依頼メンション
Slackで自分宛に送信された依頼メッセージ
2. Notionに登録されたタスク
上記メンションが解析され、Notionにタスクとして登録
以下作業手順
実現したいこと
-
Slackメンションをトリガー:
- 自分宛のメンション(特定のキーワードを含む)を受信。
- キーワード例: "対応", "お願い", "緊急", "依頼"。
-
Gemini-Proでタスク解析:
- メンション内容を解析し、タスク名・説明・期限日を抽出。
-
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"
}
解析対象:
- メンションに期限が含まれていない場合、"期限日"は空欄にしてください。
- 備考が指定されていない場合は空欄にしてください。
- 必要に応じて備考を補足してください。
`;
}
難しかった部分
-
Slackメンションの無限ループ:
- 同じメンションが何度も処理されてしまい、Notionにタスクが無限に登録される問題が発生。
-
解決策: Slackイベントの
client_msg_id
やevent_ts
を利用して処理済みのメンションをSet
で管理。
-
JSONのパースエラー:
- Geminiのレスポンスに含まれる余分なバッククォートやフォーマットミスでエラーが発生。
- 解決策: レスポンスの前後の整形処理を追加。
工夫した部分
-
イベントIDで重複処理を防止:
- Slackイベントごとに一意のIDを使い、処理済みかを追跡。
-
キーワードフィルタリング:
- 特定のキーワード(例: "対応", "お願い", "緊急", "依頼")を使い、タスク化対象を絞り込む。
-
柔軟なJSONパース処理:
- Geminiのレスポンスフォーマットに合わせて柔軟にデータを整形するロジックを実装。