Back to list
Claude AI を活用した WhatsApp チャット分析ツールの構築方法
How I Built a WhatsApp Chat Analyzer Powered by Claude AI
Translated: 2026/3/15 4:00:20
Japanese Translation
私は MatchMGT という、デートアプリユーザー用のパーソナル CRM を構築中です。真剣にデートアプリを使う立場のあなたは、きっとこの課題に遭遇したはずです。
WhatsApp は、数百通のメッセージをスクロールする代わりに、チャットを .txt ファイルとしてエクスポートする機能を持っています。出力形式は以下のようになります。
各行には、タイムスタンプ、送信者名、メッセージ内容が含まれています。
私の最初のタスクは、以下の処理を行う Worker を構築することでした。この Worker は生テキストを解析器に渡します。
- 各行を日付/送信者/内容の正規表現と一致させる
- システムメッセージ(「メッセージはエンドツーエンド暗号化されています」「メディアが省略されました」等)をスキップする
- 多行メッセージ(タイムスタンプなしの継続行)に対応する
- 清潔な { date, sender, content } オブジェクトの配列を返す
```
function parseWhatsApp(rawText, monthsBack = 6, sinceDate = null) {
const lines = rawText.split("\n");
const messages = [];
const cutoff = sinceDate || (() => {
const d = new Date();
d.setMonth(d.getMonth() - monthsBack);
return d;
})();
const regex = /^(\d{1,2}\/\d{1,2}\/\d{2,4}),?\s+(\d{1,2}:\d{2}(?:::\d{2})?(?:\s?[AP]M)?)\s+-\s+([^:]+):\s+(.+)$/i;
for (const line of lines) {
const match = line.match(regex);
if (match) {
const [, dateStr, timeStr, sender, content] = match;
const date = new Date(`${dateStr} ${timeStr}`);
if (isNaN(date.getTime())) continue;
if (sinceDate ? date <= cutoff : date < cutoff) continue;
if (content.includes("end-to-end") || content === "") continue;
messages.push({ date, sender: sender.trim(), content: content.trim() });
}
else if (messages.length > 0) {
const trimmed = line.trim();
if (trimmed) messages[messages.length - 1].content += "\n" + trimmed;
}
}
return messages;
}
```
これらが解析された後、メッセージを読みやすいブロックに整形し、トークンコストを考慮して AI へ送信します。長い WhatsApp の会話には数千通のメッセージが含まれる可能性があるため、40,000 文字に制限をかけます。チャットがそれを超えている場合は、最初の 50 通のみを使用します。
```
function formatWhatsAppForAI(messages, maxChars = 40000) {
let text = `Conversation from ${from} to ${to} (${messages.length} messages):\n\n`;
let selected = messages;
if (messages.length > 500) {
selected = [...messages.slice(0, 50), ...messages.slice(-450)];
text += `[Representative sample of ${selected.length} messages]\n\n`;
}
for (const msg of selected) {
const line = `${msg.sender}: ${msg.content}\n`;
if ((text + line).length > maxChars) break;
text += line;
}
return text;
}
```
その後、Cloudflare Worker からの実際の Claude 呼び出しが実行されます。
```
async function callClaude(apiKey, systemPrompt, userMessage) {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 2000,
system: systemPrompt,
messages: [{
role: "user",
content: userMessage
}]
}),
});
const data = await response.json();
return data.content?.[0]?.text || "";
}
```
システムプロンプトは、Claude に無効でない JSON 形式でのみ回答することを指示し、以下のような構造を要求します。
```
{
"interests": ["hiking", "indie films", "cooking"],
"personality_traits": ["curious", "introverted", "witty"],
"date_ideas": ["visit a farmers market", "cooking class together"],
"conversation_topics": ["her trip to Patagonia", "that book she mentioned"],
"gift_ideas": ["a cookbook", "a plant"],
"summary": "Sofia is curious and thoughtful. Conversations flow naturally around culture and food."
}
```
Markdown や追加テキストはありません。単に JSON のみです。これにより、フロントエンドでの解析が容易になります。
1. JSON レスポンスの常に検証を行う
2. 40k 文字制限はコストの判断であり、技術的な制限ではない
3. 日付解析が最も困難な部分である
4. AI はバックエンドで実行し、ブラウザ上では実行しない
ユーザーは WhatsApp のエクスポートを貼り付けるだけで、相手についての構造化されたプロファイルを戻ってきます。これは、実際に利用してから初めて、それだけ便利な機能だと気づくようなものです。同様のものを構築したい方、あるいは完全な製品を見てみたい方は、MatchMGT をチェックしてみてください。無料から始められ、クレジットカードは不要です。Cloudflare に関する質問に答えて乐意です。
Original Content
I'm building MatchMGT — a personal CRM for dating app users. Think of it as If you use dating apps seriously, you've been there: you matched with someone I wanted to solve that. Instead of scrolling through hundreds of messages WhatsApp lets you export any chat as a .txt file. The format looks like this: Each line has a timestamp, sender name, and message content. My first task was The Worker receives the raw text and runs it through a parser that: Matches each line against a date/sender/content regex Skips system messages ("Messages are end-to-end encrypted", multimedia omitted, etc.) Handles multiline messages (continuation lines with no timestamp) Returns a clean array of { date, sender, content } objects function parseWhatsApp(rawText, monthsBack = 6, sinceDate = null) { const lines = rawText.split("\n"); const messages = []; const cutoff = sinceDate || (() => { const d = new Date(); d.setMonth(d.getMonth() - monthsBack); return d; })(); const regex = /^(\d{1,2}\/\d{1,2}\/\d{2,4}),?\s+(\d{1,2}:\d{2}(?::\d{2})?(?:\s?[AP]M)?)\s+-\s+([^:]+):\s+(.+)$/i; for (const line of lines) { const match = line.match(regex); if (match) { const [, dateStr, timeStr, sender, content] = match; const date = new Date(`${dateStr} ${timeStr}`); if (isNaN(date.getTime())) continue; if (sinceDate ? date <= cutoff : date < cutoff) continue; if (content.includes("end-to-end") || content === "") continue; messages.push({ date, sender: sender.trim(), content: content.trim() }); } else if (messages.length > 0) { const trimmed = line.trim(); if (trimmed) messages[messages.length - 1].content += "\n" + trimmed; } } return messages; } Once parsed, I format the messages into a readable block and send them to token cost. A long WhatsApp conversation can have thousands of messages. I cap it at 40,000 characters, and if the chat is longer, I take the first 50 messages function formatWhatsAppForAI(messages, maxChars = 40000) { let text = `Conversation from ${from} to ${to} (${messages.length} messages):\n\n`; let selected = messages; if (messages.length > 500) { selected = [...messages.slice(0, 50), ...messages.slice(-450)]; text += `[Representative sample of ${selected.length} messages]\n\n`; } for (const msg of selected) { const line = `${msg.sender}: ${msg.content}\n`; if ((text + line).length > maxChars) break; text += line; } return text; } Then the actual Claude call from the Cloudflare Worker: async function callClaude(apiKey, systemPrompt, userMessage) { const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01", }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", max_tokens: 2000, system: systemPrompt, messages: [{ role: "user", content: userMessage }], }), }); const data = await response.json(); return data.content?.[0]?.text || ""; } The system prompt instructs Claude to respond only in valid JSON with this structure: { "interests": ["hiking", "indie films", "cooking"], "personality_traits": ["curious", "introverted", "witty"], "date_ideas": ["visit a farmers market", "cooking class together"], "conversation_topics": ["her trip to Patagonia", "that book she mentioned"], "gift_ideas": ["a cookbook", "a plant"], "summary": "Sofia is curious and thoughtful. Conversations flow naturally around culture and food." } No markdown. No extra text. Just the JSON. This makes frontend parsing trivial. 1. Always validate the JSON response. 2. The 40k char limit is a cost decision, not a technical one. 3. Date parsing is the hardest part. 4. Run AI on the backend, not the browser. Users paste their WhatsApp export and get back a structured profile of the person It's one of those features that sounds gimmicky until you actually use it and realize If you're building something similar or just want to see the full product, check out MatchMGT — it's free to start, no credit card needed. Happy to answer questions about the Cloudflare Workers setup, the parsing logic, or the prompt engineering in the comments. 👇