diff --git a/CLAUDE.md b/CLAUDE.md index 35f6a25..056ca56 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,8 @@ # CLAUDE.md +> ๐Ÿ”ง **๊ฐœ๋ฐœ์ž์šฉ ๋ฌธ์„œ**: ๊ธฐ์ˆ  ์ƒ์„ธ, ์ฝ”๋“œ ํŒจํ„ด, ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… +> ๐Ÿ“– **[README.md](./README.md)**: ๊ธฐ๋Šฅ ์†Œ๊ฐœ, ๋ฐฐํฌ ๊ฐ€์ด๋“œ, ์‚ฌ์šฉ๋ฒ• + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Auto-Read on Start @@ -69,8 +72,8 @@ npm run chat # CLI ํ…Œ์ŠคํŠธ ํด๋ผ์ด์–ธํŠธ **CLI ํ…Œ์ŠคํŠธ ํด๋ผ์ด์–ธํŠธ:** ```bash -# ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ • -export WEBHOOK_SECRET="..." # Vault: secret/data/telegram-bot +# .env ํŒŒ์ผ ์ƒ์„ฑ (์ตœ์ดˆ 1ํšŒ) +echo 'WEBHOOK_SECRET=...' > .env # Vault: secret/data/telegram-bot # ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ npm run chat @@ -84,6 +87,7 @@ npm run chat "๋‚ ์”จ ์•Œ๋ ค์ค˜" wrangler secret put BOT_TOKEN # Telegram Bot Token wrangler secret put WEBHOOK_SECRET # Webhook ๊ฒ€์ฆ์šฉ wrangler secret put OPENAI_API_KEY # OpenAI API ํ‚ค +wrangler secret put NAMECHEAP_API_KEY # namecheap-api ๋ž˜ํผ ์ธ์ฆ ํ‚ค ``` **Webhook ์„ค์ •:** @@ -223,7 +227,7 @@ Telegram Webhook โ†’ Security Validation โ†’ Command/Message Router | ๋ฌธ์„œ | `lookup_docs` | Context7 | ๋ฌธ์„œ, ์‚ฌ์šฉ๋ฒ•, API | | ๋„๋ฉ”์ธ | `manage_domain` | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ โ†’ Namecheap | ๋„๋ฉ”์ธ, ๋„ค์ž„์„œ๋ฒ„, WHOIS | | ๋„๋ฉ”์ธ ์ถ”์ฒœ | `suggest_domains` | GPT + Namecheap | **๋„๋ฉ”์ธ ์ถ”์ฒœ, ๋„๋ฉ”์ธ ์ œ์•ˆ, ๋„๋ฉ”์ธ ์•„์ด๋””์–ด** | -| ์˜ˆ์น˜๊ธˆ | `manage_deposit` | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ โ†’ D1 | **์ž…๊ธˆ, ์ถฉ์ „, ์ž”์•ก, ๊ณ„์ขŒ, ์†ก๊ธˆ** | +| ์˜ˆ์น˜๊ธˆ | `manage_deposit` | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ | **์ž…๊ธˆ, ์ถฉ์ „, ์ž”์•ก, ๊ณ„์ขŒ, ์†ก๊ธˆ** | **Data Layer (D1 SQLite):** | ํ…Œ์ด๋ธ” | ์šฉ๋„ | ์ฃผ์š” ์ปฌ๋Ÿผ | @@ -351,18 +355,6 @@ case 'new_tool': **๋กœ๊ทธ:** `[search_web] ๋ฒˆ์—ญ: "ํŒ๊ณจ๋ฆฐ VPN" โ†’ "Pangolin VPN"` -### Deposit Agent ํ”„๋กฌํ”„ํŠธ ์ˆ˜์ • ๋ฐฉ๋ฒ• -```bash -# Vault์—์„œ API ํ‚ค ์กฐํšŒ -curl -s -H "X-Vault-Token: hvs.xxx" https://vault.anvil.it.com/v1/secret/data/openai - -# Assistant ํ”„๋กฌํ”„ํŠธ ์—…๋ฐ์ดํŠธ -curl -X POST 'https://api.openai.com/v1/assistants/asst_XMoVGU7ZwRpUPI6PHGvRNm8E' \ - -H 'Authorization: Bearer sk-xxx' \ - -H 'OpenAI-Beta: assistants=v2' \ - -d @update-agent.json -``` - --- ## Configuration @@ -375,7 +367,6 @@ curl -X POST 'https://api.openai.com/v1/assistants/asst_XMoVGU7ZwRpUPI6PHGvRNm8E | `DOMAIN_OWNER_ID` | - | ๋„๋ฉ”์ธ ๊ด€๋ฆฌ ๊ถŒํ•œ Telegram ID | | `DEPOSIT_ADMIN_ID` | - | ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ ๊ถŒํ•œ Telegram ID | | `WEBHOOK_SECRET` | - | Telegram Webhook ์ธ์ฆ (wrangler secret, Vault: telegram-bot) | -| `BANK_API_SECRET` | - | ์ž…๊ธˆ ์•Œ๋ฆผ API ์ธ์ฆ ํ‚ค (wrangler secret) | | `BRAVE_API_KEY` | - | Brave Search API ํ‚ค (wrangler secret) | | `DEPOSIT_API_SECRET` | - | Deposit API ์ธ์ฆ ํ‚ค (namecheap-api์šฉ, wrangler secret) | @@ -392,8 +383,7 @@ curl -X POST 'https://api.openai.com/v1/assistants/asst_XMoVGU7ZwRpUPI6PHGvRNm8E | wttr.in | ๋‚ ์”จ | wttr.in | - | | Brave Search | ๊ฒ€์ƒ‰ | api.search.brave.com | Free AI ํ”Œ๋žœ (2,000/์›”) | | Vault | API ํ‚ค ๊ด€๋ฆฌ | vault.anvil.it.com | - | -| Gmail | ์ž…๊ธˆ SMS ์ˆ˜์‹  | deposit.anvil@gmail.com | Apps Script ์—ฐ๋™ | -| Apps Script | Gmail โ†’ Worker ์—ฐ๋™ | script.google.com | 1๋ถ„๋งˆ๋‹ค ์‹คํ–‰, message_id ์ค‘๋ณต ๋ฐฉ์ง€ | +| Email Routing | ์ž…๊ธˆ SMS ์ˆ˜์‹  | Cloudflare Email Routing | Worker email handler๋กœ ์ง์ ‘ ์ฒ˜๋ฆฌ | ### Cloudflare AI Gateway @@ -423,10 +413,8 @@ URL: gateway.ai.cloudflare.com/v1/{account_id}/telegram-bot/openai/... โ†“ ๋งค์นญ O โ†’ confirmed + ์ž”์•กโ†‘ | ๋งค์นญ X โ†’ pending -[์‹œ๋‚˜๋ฆฌ์˜ค 2: SMS ๋จผ์ € - Gmail โ†’ Apps Script โ†’ Worker] -์€ํ–‰ SMS โ†’ Gmail(deposit.anvil@gmail.com) โ†’ Apps Script (1๋ถ„๋งˆ๋‹ค) - โ†“ - POST /api/bank-notification +[์‹œ๋‚˜๋ฆฌ์˜ค 2: SMS ๋จผ์ € - Email Routing] +์€ํ–‰ SMS โ†’ ๋ฉ”์ผ ์ „๋‹ฌ โ†’ Cloudflare Email Routing โ†’ Worker (email handler) โ†“ ํŒŒ์‹ฑ โ†’ bank_notifications ์ €์žฅ โ†“ @@ -442,22 +430,9 @@ URL: gateway.ai.cloudflare.com/v1/{account_id}/telegram-bot/openai/... | ์ž๋™ ๋งค์นญ ์„ฑ๊ณต | โœ… ์ž…๊ธˆ์•ก + ํ˜„์žฌ ์ž”์•ก | โœ… ์ž…๊ธˆ ์ •๋ณด + ๋งค์นญ ์™„๋ฃŒ | | ๋งค์นญ ๋Œ€๊ธฐ (SMS๋งŒ) | - | โœ… ์ž…๊ธˆ ์ •๋ณด + ๋Œ€๊ธฐ ์ƒํƒœ | -**Gmail โ†’ Worker ์—ฐ๋™:** -- Gmail ๊ณ„์ •: `deposit.anvil@gmail.com` -- Apps Script: 1๋ถ„๋งˆ๋‹ค `is:unread ์ž…๊ธˆ` ๊ฒ€์ƒ‰ โ†’ Worker API ํ˜ธ์ถœ -- ์ค‘๋ณต ๋ฐฉ์ง€: Gmail message_id ๊ธฐ๋ฐ˜ - -**API ์—”๋“œํฌ์ธํŠธ:** -``` -POST /api/bank-notification -Content-Type: application/json - -{ - "content": "[Web๋ฐœ์‹ ]\nํ•˜๋‚˜,01/16, 23:30\n427******27104\n์ž…๊ธˆ5์›\nํ™ฉ๋ณ‘ํ•˜", - "messageId": "19bc737b3415596a", - "secret": "BANK_API_SECRET ๊ฐ’" -} -``` +**Email Routing ์„ค์ •:** +- Cloudflare Dashboard โ†’ Email โ†’ Email Routing โ†’ Routes +- ์ˆ˜์‹  ์ฃผ์†Œ โ†’ Worker: `telegram-summary-bot` ๋ผ์šฐํŒ… **์ž…๊ธˆ ๊ณ„์ขŒ:** ํ•˜๋‚˜์€ํ–‰ 427-910018-27104 (์ฃผ์‹ํšŒ์‚ฌ ์•„์ด์–ธํด๋ž˜๋“œ) - Vault ๊ฒฝ๋กœ: `secret/companies/ironclad-corp` diff --git a/README.md b/README.md index 4af20bf..1a2d408 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ > Cloudflare Workers + D1 + OpenAI๋ฅผ ํ™œ์šฉํ•œ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๊ธฐ๋ฐ˜ ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ +**๐Ÿ“– ์ด ๋ฌธ์„œ**: ๊ธฐ๋Šฅ ์†Œ๊ฐœ, ๋ฐฐํฌ ๊ฐ€์ด๋“œ, ์‚ฌ์šฉ๋ฒ• (์‚ฌ์šฉ์ž/์šด์˜์ž์šฉ) +**๐Ÿ”ง [CLAUDE.md](./CLAUDE.md)**: ๊ธฐ์ˆ  ์ƒ์„ธ, ์ฝ”๋“œ ํŒจํ„ด, ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… (๊ฐœ๋ฐœ์ž์šฉ) + ## ๋ชฉ์ฐจ 1. [๊ฐœ์š”](#๊ฐœ์š”) @@ -26,8 +29,7 @@ - **Context7 ์—ฐ๋™**: ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ณต์‹ ๋ฌธ์„œ ์‹ค์‹œ๊ฐ„ ์กฐํšŒ - **๋™์  ๋„๊ตฌ ๋กœ๋”ฉ**: ๋ฉ”์‹œ์ง€ ํ‚ค์›Œ๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„์š”ํ•œ ๋„๊ตฌ๋งŒ ์„ ํƒํ•˜์—ฌ ํ† ํฐ ์ ˆ์•ฝ - **๋„๋ฉ”์ธ ์ถ”์ฒœ**: GPT๊ฐ€ ์ฐฝ์˜์  ๋„๋ฉ”์ธ ์ƒ์„ฑ โ†’ ๊ฐ€์šฉ์„ฑ ์ž๋™ ํ™•์ธ โ†’ ๊ฐ€๊ฒฉ๊ณผ ํ•จ๊ป˜ ์ œ์•ˆ -- **Deposit Agent**: OpenAI Assistants API ๊ธฐ๋ฐ˜ ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ ์—์ด์ „ํŠธ ์—ฐ๋™ -- **์˜ˆ์น˜๊ธˆ ์‹œ์Šคํ…œ**: ์€ํ–‰ ์ž…๊ธˆ ์ž๋™ ๊ฐ์ง€ + ์‚ฌ์šฉ์ž ์‹ ๊ณ  ๋งค์นญ์œผ๋กœ ์ž๋™ ์ถฉ์ „ +- **์˜ˆ์น˜๊ธˆ ์‹œ์Šคํ…œ**: ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ, ์€ํ–‰ ์ž…๊ธˆ ์ž๋™ ๊ฐ์ง€ + ์‚ฌ์šฉ์ž ์‹ ๊ณ  ๋งค์นญ์œผ๋กœ ์ž๋™ ์ถฉ์ „ - **Email Worker**: SMS โ†’ ๋ฉ”์ผ โ†’ ์ž๋™ ํŒŒ์‹ฑ์œผ๋กœ ์ž…๊ธˆ ์•Œ๋ฆผ ์ฒ˜๋ฆฌ - **๋ฌดํ•œ ์ปจํ…์ŠคํŠธ**: ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ(3๊ฐœ)๋กœ ํ”„๋กœํ•„ ์œ ์ง€, ๋ฌด์ œํ•œ ๋Œ€ํ™” ๊ธฐ์–ต - **๊ฐœ์ธํ™” ์‘๋‹ต**: ํ”„๋กœํ•„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งž์ถคํ˜• AI ์‘๋‹ต ์ œ๊ณต @@ -43,7 +45,7 @@ | **Context7** | ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฌธ์„œ ์กฐํšŒ API | | **๋„๋ฉ”์ธ ๊ด€๋ฆฌ** | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ โ†’ Namecheap API | | **๋„๋ฉ”์ธ ์ถ”์ฒœ** | GPT + Namecheap API (์ฝ”๋“œ ๋ ˆ๋ฒจ) | -| **Deposit Agent** | ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ (OpenAI Assistants) | +| **์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ** | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ โ†’ D1 | | **Namecheap API** | ๋„๋ฉ”์ธ ์กฐํšŒ/๊ฐ€์šฉ์„ฑ/๊ฐ€๊ฒฉ ๋ฐฑ์—”๋“œ | | **Email Workers** | SMS โ†’ ๋ฉ”์ผ ํŒŒ์‹ฑ (์ž…๊ธˆ ์•Œ๋ฆผ) | | **Workers AI** | ํด๋ฐฑ์šฉ (Llama 3.1 8B) | @@ -148,7 +150,7 @@ OpenAI Function Calling์„ ํ†ตํ•ด AI๊ฐ€ ์ž๋™์œผ๋กœ ํ•„์š”ํ•œ ๋„๊ตฌ๋ฅผ ํ˜ธ์ถœ | **๋ฌธ์„œ** | "React hooks ์‚ฌ์šฉ๋ฒ•", "OpenAI API ์˜ˆ์ œ" | Context7 | | **๋„๋ฉ”์ธ** | "๋„๋ฉ”์ธ ๋ชฉ๋ก", "anvil.it.com ๋„ค์ž„์„œ๋ฒ„", ".com ๊ฐ€๊ฒฉ", "google.com whois" | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ + WHOIS API | | **๋„๋ฉ”์ธ ์ถ”์ฒœ** | "์ปคํ”ผ์ˆ ๋„๋ฉ”์ธ ์ถ”์ฒœํ•ด์ค˜", "์Šคํƒ€ํŠธ์—… ๋„๋ฉ”์ธ ์•„์ด๋””์–ด" | GPT + Namecheap | -| **์˜ˆ์น˜๊ธˆ** | "์ž”์•ก ํ™•์ธ", "์ถฉ์ „ํ•˜๊ณ  ์‹ถ์–ด", "10000์› ์ž…๊ธˆํ–ˆ์–ด" | D1 + Email Worker | +| **์˜ˆ์น˜๊ธˆ** | "์ž”์•ก ํ™•์ธ", "์ถฉ์ „ํ•˜๊ณ  ์‹ถ์–ด", "10000์› ์ž…๊ธˆํ–ˆ์–ด" | ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ | ### ๋™์ž‘ ๋ฐฉ์‹ @@ -168,6 +170,31 @@ OpenAI: ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์—ฐ์–ด๋กœ ์‘๋‹ต ์ƒ์„ฑ ์‘๋‹ต: "๐ŸŒค ์„œ์šธ ๋‚ ์”จ\n์˜จ๋„: 5ยฐC\n์Šต๋„: 45%..." ``` +### ๋™์  ๋„๊ตฌ ๋กœ๋”ฉ + +๋ฉ”์‹œ์ง€ ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ํ•„์š”ํ•œ ๋„๊ตฌ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋กœ๋”ฉํ•ฉ๋‹ˆ๋‹ค. (ํ† ํฐ 40% ์ ˆ์•ฝ) + +| ์นดํ…Œ๊ณ ๋ฆฌ | ๋„๊ตฌ | ๊ฐ์ง€ ํŒจํ„ด | +|----------|------|-----------| +| domain | manage_domain, suggest_domains | ๋„๋ฉ”์ธ, ๋„ค์ž„์„œ๋ฒ„, whois, .com | +| deposit | manage_deposit | ์ž…๊ธˆ, ์ถฉ์ „, ์ž”์•ก, ๊ณ„์ขŒ | +| weather | get_weather | ๋‚ ์”จ, ๊ธฐ์˜จ, ๋น„, ๋ˆˆ | +| search | search_web, lookup_docs | ๊ฒ€์ƒ‰, ์ฐพ์•„, ๋ญ์•ผ, ๊ฐ€๊ฒฉ | +| utility | get_current_time, calculate | (ํ•ญ์ƒ ํฌํ•จ) | + +ํŒจํ„ด ๋งค์นญ ์—†์œผ๋ฉด ์ „์ฒด ๋„๊ตฌ ์‚ฌ์šฉ (ํด๋ฐฑ) + +### AI Gateway + +OpenAI API ํ˜ธ์ถœ์„ Cloudflare AI Gateway๋ฅผ ํ†ตํ•ด ํ”„๋ก์‹œํ•˜์—ฌ ์ง€์—ญ ์ œํ•œ์„ ์šฐํšŒํ•ฉ๋‹ˆ๋‹ค. + +``` +Gateway ID: telegram-bot +URL: gateway.ai.cloudflare.com/v1/{account_id}/telegram-bot/openai/... +``` + +**๋Œ€์‹œ๋ณด๋“œ**: Cloudflare Dashboard โ†’ AI โ†’ AI Gateway โ†’ telegram-bot + --- ## ์˜ˆ์น˜๊ธˆ ์‹œ์Šคํ…œ @@ -202,7 +229,7 @@ OpenAI: ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์—ฐ์–ด๋กœ ์‘๋‹ต ์ƒ์„ฑ ``` [์‹œ๋‚˜๋ฆฌ์˜ค 2: ์€ํ–‰ SMS๊ฐ€ ๋จผ์ € ๋„์ฐฉ] -์€ํ–‰ SMS โ†’ Gmail โ†’ Apps Script +์€ํ–‰ SMS โ†’ ๋ฉ”์ผ ์ „๋‹ฌ โ†’ Cloudflare Email Routing โ†’ Worker (email handler) โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” @@ -280,57 +307,23 @@ OpenAI: ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ์ž์—ฐ์–ด๋กœ ์‘๋‹ต ์ƒ์„ฑ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ ``` -### Gmail โ†’ Apps Script โ†’ Worker ์—ฐ๋™ +### Cloudflare Email Routing -SMS๋ฅผ Gmail๋กœ ์ „๋‹ฌ๋ฐ›์•„ Apps Script์—์„œ Worker API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. +SMS๋ฅผ ๋ฉ”์ผ๋กœ ์ „๋‹ฌ๋ฐ›์•„ Worker์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. **ํ๋ฆ„:** ``` -์€ํ–‰ SMS โ†’ Gmail(deposit.anvil@gmail.com) โ†’ Apps Script (1๋ถ„๋งˆ๋‹ค) - โ†“ - POST /api/bank-notification โ†’ DB ์ €์žฅ โ†’ ์ž๋™ ๋งค์นญ - โ†“ - ๋งค์นญ ์„ฑ๊ณต โ†’ ์‚ฌ์šฉ์ž/๊ด€๋ฆฌ์ž Telegram ์•Œ๋ฆผ +์€ํ–‰ SMS โ†’ ๋ฉ”์ผ ์ „๋‹ฌ โ†’ Cloudflare Email Routing โ†’ Worker (email handler) + โ†“ + SMS ํŒŒ์‹ฑ โ†’ DB ์ €์žฅ โ†’ ์ž๋™ ๋งค์นญ + โ†“ + ๋งค์นญ ์„ฑ๊ณต โ†’ ์‚ฌ์šฉ์ž/๊ด€๋ฆฌ์ž Telegram ์•Œ๋ฆผ ``` -**Apps Script ์ฝ”๋“œ:** -```javascript -function checkBankEmails() { - var threads = GmailApp.search('is:unread ์ž…๊ธˆ', 0, 10); - - for (var i = 0; i < threads.length; i++) { - var messages = threads[i].getMessages(); - - for (var j = 0; j < messages.length; j++) { - var message = messages[j]; - if (!message.isUnread()) continue; - - var messageId = message.getId(); - var body = message.getPlainBody(); - - try { - UrlFetchApp.fetch( - 'https://telegram-summary-bot.kappa-d8e.workers.dev/api/bank-notification', - { - method: 'POST', - contentType: 'application/json', - payload: JSON.stringify({ - content: body, - messageId: messageId, - secret: 'BANK_API_SECRET ๊ฐ’' - }) - } - ); - } catch (e) { - console.log('Error: ' + e); - } - } - threads[i].markRead(); - } -} -``` - -**ํŠธ๋ฆฌ๊ฑฐ ์„ค์ •:** ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ โ†’ ๋ถ„ ํƒ€์ด๋จธ โ†’ 1๋ถ„๋งˆ๋‹ค +**์„ค์ • ๋ฐฉ๋ฒ•:** +1. Cloudflare Dashboard โ†’ Email โ†’ Email Routing โ†’ Routes +2. ์ˆ˜์‹  ์ฃผ์†Œ ์„ค์ • (์˜ˆ: `deposit@your-domain.com`) +3. Worker๋กœ ๋ผ์šฐํŒ…: `telegram-summary-bot` **์ง€์› ์€ํ–‰ SMS ํŒจํ„ด:** - ํ•˜๋‚˜์€ํ–‰ (Web๋ฐœ์‹ ): `[Web๋ฐœ์‹ ] ํ•˜๋‚˜,01/16, 23:30 427******27104 ์ž…๊ธˆ5์› ํ™ฉ๋ณ‘ํ•˜` @@ -540,14 +533,14 @@ wrangler secret put WEBHOOK_SECRET # OpenAI API Key (ํ•„์ˆ˜) wrangler secret put OPENAI_API_KEY -# ์ž…๊ธˆ ์•Œ๋ฆผ API Secret (Apps Script ์—ฐ๋™์šฉ) -wrangler secret put BANK_API_SECRET - # Brave Search API Key wrangler secret put BRAVE_API_KEY # Deposit API Secret (namecheap-api ์—ฐ๋™์šฉ) wrangler secret put DEPOSIT_API_SECRET + +# namecheap-api ๋ž˜ํผ ์ธ์ฆ ํ‚ค (๋„๋ฉ”์ธ ์ถ”์ฒœ์šฉ) +wrangler secret put NAMECHEAP_API_KEY ``` ### Vault ์—ฐ๋™ (์„ ํƒ) @@ -582,6 +575,19 @@ curl https://telegram-summary-bot.kappa-d8e.workers.dev/setup-webhook curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` +### 6. CLI ํ…Œ์ŠคํŠธ (์„ ํƒ) + +```bash +# .env ํŒŒ์ผ ์ƒ์„ฑ (์ตœ์ดˆ 1ํšŒ) +echo 'WEBHOOK_SECRET=...' > .env # Vault: secret/telegram-bot + +# ๋Œ€ํ™”ํ˜• ๋ชจ๋“œ +npm run chat + +# ๋‹จ์ผ ๋ฉ”์‹œ์ง€ ๋ชจ๋“œ +npm run chat "์•ˆ๋…•" +``` + --- ## ๋ณด์•ˆ ์„ค์ • @@ -680,7 +686,7 @@ database_id = "c285bb5b-888b-405d-b36f-475ae5aed20e" | `/webhook-info` | GET | Webhook ์ƒํƒœ | | `/setup-webhook` | GET | Webhook ์„ค์ • | | `/webhook` | POST | Telegram Webhook | -| `/api/bank-notification` | POST | ์ž…๊ธˆ ์•Œ๋ฆผ API (Apps Script ์—ฐ๋™) | +| `/api/bank-notification` | POST | ์ž…๊ธˆ ์•Œ๋ฆผ API (๋ ˆ๊ฑฐ์‹œ, Email Routing์œผ๋กœ ๋Œ€์ฒด) | | `/api/deposit/balance` | GET | ์˜ˆ์น˜๊ธˆ ์ž”์•ก ์กฐํšŒ (namecheap-api์šฉ) | | `/api/deposit/deduct` | POST | ์˜ˆ์น˜๊ธˆ ์ฐจ๊ฐ (namecheap-api์šฉ) | diff --git a/scripts/chat.ts b/scripts/chat.ts index d9051f4..bfba813 100644 --- a/scripts/chat.ts +++ b/scripts/chat.ts @@ -2,11 +2,25 @@ /** * Telegram Bot CLI Chat Client * - Worker์˜ /api/test ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด ์ง์ ‘ ๋Œ€ํ™” - * - ์‚ฌ์šฉ๋ฒ•: npx tsx scripts/chat.ts - * ๋˜๋Š”: npx tsx scripts/chat.ts "๋ฉ”์‹œ์ง€" + * - ์‚ฌ์šฉ๋ฒ•: npm run chat + * ๋˜๋Š”: npm run chat "๋ฉ”์‹œ์ง€" */ import * as readline from 'readline'; +import * as fs from 'fs'; +import * as path from 'path'; + +// .env ํŒŒ์ผ ๋กœ๋“œ +const envPath = path.join(process.cwd(), '.env'); +if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + for (const line of envContent.split('\n')) { + const [key, ...valueParts] = line.split('='); + if (key && valueParts.length > 0) { + process.env[key.trim()] = valueParts.join('=').trim(); + } + } +} const WORKER_URL = process.env.WORKER_URL || 'https://telegram-summary-bot.kappa-d8e.workers.dev'; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; diff --git a/src/deposit-agent.ts b/src/deposit-agent.ts index f92594d..07e6adf 100644 --- a/src/deposit-agent.ts +++ b/src/deposit-agent.ts @@ -1,5 +1,8 @@ /** - * Deposit Agent - ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ ์—์ด์ „ํŠธ (OpenAI Assistants API) + * Deposit Agent - ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ (์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ) + * + * ๋ณ€๊ฒฝ ์ด๋ ฅ: + * - 2026-01: Assistants API โ†’ ์ฝ”๋“œ ์ง์ ‘ ์ฒ˜๋ฆฌ๋กœ ๋ณ€๊ฒฝ (์ง€์—ญ ์ œํ•œ ์šฐํšŒ, ์‘๋‹ต ์ผ๊ด€์„ฑ) * * ๊ธฐ๋Šฅ: * - ์ž”์•ก ์กฐํšŒ diff --git a/src/index.ts b/src/index.ts index 62a3365..cc442ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -169,112 +169,6 @@ export default { } } - // Bank Notification API (Gmail โ†’ Apps Script โ†’ Worker) - if (url.pathname === '/api/bank-notification' && request.method === 'POST') { - try { - const body = await request.json() as { content: string; secret?: string; messageId?: string }; - - // ๊ฐ„๋‹จํ•œ ์ธ์ฆ (BANK_API_SECRET ๋˜๋Š” WEBHOOK_SECRET ์‚ฌ์šฉ) - const apiSecret = (env as any).BANK_API_SECRET || env.WEBHOOK_SECRET; - if (apiSecret && body.secret !== apiSecret) { - return Response.json({ error: 'Unauthorized' }, { status: 401 }); - } - - console.log('[API] Bank notification received:', body.content?.slice(0, 100)); - - // ๋ฉ”์ผ ID๋กœ ์ค‘๋ณต ์ฒดํฌ - if (body.messageId) { - const existing = await env.DB.prepare( - 'SELECT id FROM bank_notifications WHERE message_id = ?' - ).bind(body.messageId).first(); - - if (existing) { - console.log('[API] ์ค‘๋ณต ๋ฉ”์ผ ๋ฌด์‹œ:', body.messageId); - return Response.json({ success: true, duplicate: true, messageId: body.messageId }); - } - } - - // SMS ํŒŒ์‹ฑ - const notification = parseBankSMS(body.content || ''); - if (!notification) { - console.log('[API] ํŒŒ์‹ฑ ์‹คํŒจ:', body.content); - return Response.json({ error: 'Parse failed', content: body.content }, { status: 400 }); - } - - console.log('[API] ํŒŒ์‹ฑ ๊ฒฐ๊ณผ:', notification); - - // DB์— ์ €์žฅ - const insertResult = await env.DB.prepare( - `INSERT INTO bank_notifications (bank_name, depositor_name, amount, balance_after, transaction_time, raw_message, message_id) - VALUES (?, ?, ?, ?, ?, ?, ?)` - ).bind( - notification.bankName, - notification.depositorName, - notification.amount, - notification.balanceAfter || null, - notification.transactionTime?.toISOString() || null, - notification.rawMessage, - body.messageId || null - ).run(); - - const notificationId = insertResult.meta.last_row_id; - console.log('[API] ์•Œ๋ฆผ ์ €์žฅ ์™„๋ฃŒ, ID:', notificationId); - - // ์ž๋™ ๋งค์นญ ์‹œ๋„ - const matched = await tryAutoMatch(env.DB, notificationId as number, notification); - - // ๋งค์นญ ์„ฑ๊ณต ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ - if (matched && env.BOT_TOKEN) { - const user = await env.DB.prepare( - 'SELECT telegram_id FROM users WHERE id = ?' - ).bind(matched.userId).first<{ telegram_id: string }>(); - - if (user) { - // ์—…๋ฐ์ดํŠธ๋œ ์ž”์•ก ์กฐํšŒ - const deposit = await env.DB.prepare( - 'SELECT balance FROM user_deposits WHERE user_id = ?' - ).bind(matched.userId).first<{ balance: number }>(); - - await sendMessage( - env.BOT_TOKEN, - parseInt(user.telegram_id), - `โœ… ์ž…๊ธˆ ํ™•์ธ ์™„๋ฃŒ!\n\n` + - `์ž…๊ธˆ์•ก: ${matched.amount.toLocaleString()}์›\n` + - `ํ˜„์žฌ ์ž”์•ก: ${(deposit?.balance || 0).toLocaleString()}์›\n\n` + - `๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰` - ); - } - } - - // ๊ด€๋ฆฌ์ž์—๊ฒŒ ์•Œ๋ฆผ - if (env.BOT_TOKEN && env.DEPOSIT_ADMIN_ID) { - const statusMsg = matched - ? `โœ… ์ž๋™ ๋งค์นญ ์™„๋ฃŒ! (๊ฑฐ๋ž˜ #${matched.transactionId})` - : 'โณ ๋งค์นญ ๋Œ€๊ธฐ ์ค‘ (์‚ฌ์šฉ์ž ์ž…๊ธˆ ์‹ ๊ณ  ํ•„์š”)'; - - await sendMessage( - env.BOT_TOKEN, - parseInt(env.DEPOSIT_ADMIN_ID), - `๐Ÿฆ ์ž…๊ธˆ ์•Œ๋ฆผ\n\n` + - `์€ํ–‰: ${notification.bankName}\n` + - `์ž…๊ธˆ์ž: ${notification.depositorName}\n` + - `๊ธˆ์•ก: ${notification.amount.toLocaleString()}์›\n` + - `${notification.balanceAfter ? `์ž”์•ก: ${notification.balanceAfter.toLocaleString()}์›\n` : ''}` + - `\n${statusMsg}` - ); - } - - return Response.json({ - success: true, - notification, - matched: !!matched - }); - } catch (error) { - console.error('[API] Bank notification error:', error); - return Response.json({ error: String(error) }, { status: 500 }); - } - } - // Deposit API - ์ž”์•ก ์กฐํšŒ (namecheap-api ์ „์šฉ) if (url.pathname === '/api/deposit/balance' && request.method === 'GET') { try { @@ -497,7 +391,7 @@ Documentation: https://github.com/your-repo // SMS ๋‚ด์šฉ ํŒŒ์‹ฑ const notification = parseBankSMS(rawEmail); if (!notification) { - console.log('[Email] ์€ํ–‰ SMS ํŒŒ์‹ฑ ์‹คํŒจ:', rawEmail.slice(0, 200)); + console.log('[Email] ์€ํ–‰ SMS ํŒŒ์‹ฑ ์‹คํŒจ'); return; } @@ -568,10 +462,55 @@ Documentation: https://github.com/your-repo }, }; +// Quoted-Printable UTF-8 ๋””์ฝ”๋”ฉ +function decodeQuotedPrintableUTF8(str: string): string { + // ์ค„ ์—ฐ์† ๋ฌธ์ž ์ œ๊ฑฐ + str = str.replace(/=\r?\n/g, ''); + + // =XX ํŒจํ„ด์„ ๋ฐ”์ดํŠธ๋กœ ๋ณ€ํ™˜ + const bytes: number[] = []; + let i = 0; + while (i < str.length) { + if (str[i] === '=' && i + 2 < str.length) { + const hex = str.slice(i + 1, i + 3); + if (/^[0-9A-Fa-f]{2}$/.test(hex)) { + bytes.push(parseInt(hex, 16)); + i += 3; + continue; + } + } + bytes.push(str.charCodeAt(i)); + i++; + } + + // UTF-8 ๋ฐ”์ดํŠธ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ + try { + return new TextDecoder('utf-8').decode(new Uint8Array(bytes)); + } catch { + return str; + } +} + // ์€ํ–‰ SMS ํŒŒ์‹ฑ ํ•จ์ˆ˜ function parseBankSMS(content: string): BankNotification | null { - // ์ด๋ฉ”์ผ์—์„œ SMS ๋ณธ๋ฌธ ์ถ”์ถœ (์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์ณ ์žˆ์„ ์ˆ˜ ์žˆ์Œ) - const text = content.replace(/\r\n/g, '\n').replace(/=\n/g, ''); + // MIME ์ด๋ฉ”์ผ ์ „์ฒ˜๋ฆฌ + let text = content; + + // Quoted-Printable UTF-8 ๋””์ฝ”๋”ฉ + text = decodeQuotedPrintableUTF8(text); + + // HTML
ํƒœ๊ทธ๋ฅผ ์ค„๋ฐ”๊ฟˆ์œผ๋กœ ๋ณ€ํ™˜ + text = text.replace(//gi, '\n'); + + // ์ค„๋ฐ”๊ฟˆ ์ •๊ทœํ™” + text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + // [Web๋ฐœ์‹ ] ๋˜๋Š” ์€ํ–‰ ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ๋Š” ๋ถ€๋ถ„๋งŒ ์ถ”์ถœ + const smsStartMatch = text.match(/\[Web๋ฐœ์‹ \]|\[ํ•˜๋‚˜์€ํ–‰\]|\[KB\]|\[์‹ ํ•œ\]|\[์šฐ๋ฆฌ\]|\[๋†ํ˜‘\]/); + if (smsStartMatch && smsStartMatch.index !== undefined) { + // SMS ์‹œ์ž‘์ ๋ถ€ํ„ฐ 500์ž ์ถ”์ถœ + text = text.slice(smsStartMatch.index, smsStartMatch.index + 500); + } // ํ•˜๋‚˜์€ํ–‰ Web๋ฐœ์‹  ํŒจํ„ด (์—ฌ๋Ÿฌ ์ค„): // [Web๋ฐœ์‹ ] diff --git a/src/openai-service.ts b/src/openai-service.ts index 804229a..457befd 100644 --- a/src/openai-service.ts +++ b/src/openai-service.ts @@ -253,8 +253,7 @@ function selectToolsForMessage(message: string): typeof tools { } // ๋„๋ฉ”์ธ ์ถ”์ฒœ ํ•จ์ˆ˜ -async function suggestDomains(keywords: string, apiKey: string): Promise { - const namecheapApiKey = '05426957210b42e752950f565ea82a3f48df9cccfdce9d82cd9817011968076e'; +async function suggestDomains(keywords: string, apiKey: string, namecheapApiKey: string): Promise { const namecheapApiUrl = 'https://namecheap-api.anvil.it.com'; const TARGET_COUNT = 10; const MAX_RETRIES = 3; @@ -1123,11 +1122,15 @@ async function executeTool(name: string, args: Record, env?: Env console.log('[suggest_domains] ์‹œ์ž‘:', { keywords }); if (!env?.OPENAI_API_KEY) { - return '๐Ÿšซ ๋„๋ฉ”์ธ ์ถ”์ฒœ ๊ธฐ๋Šฅ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.'; + return '๐Ÿšซ ๋„๋ฉ”์ธ ์ถ”์ฒœ ๊ธฐ๋Šฅ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (OPENAI_API_KEY ๋ฏธ์„ค์ •)'; + } + + if (!env?.NAMECHEAP_API_KEY) { + return '๐Ÿšซ ๋„๋ฉ”์ธ ์ถ”์ฒœ ๊ธฐ๋Šฅ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. (NAMECHEAP_API_KEY ๋ฏธ์„ค์ •)'; } try { - const result = await suggestDomains(keywords, env.OPENAI_API_KEY); + const result = await suggestDomains(keywords, env.OPENAI_API_KEY, env.NAMECHEAP_API_KEY); console.log('[suggest_domains] ์™„๋ฃŒ:', result?.slice(0, 100)); return result; } catch (error) { diff --git a/src/types.ts b/src/types.ts index d7cdd6f..dd5aac6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,6 @@ export interface Env { OPENAI_API_KEY?: string; NAMECHEAP_API_KEY?: string; DOMAIN_OWNER_ID?: string; - DEPOSIT_AGENT_ID?: string; DEPOSIT_ADMIN_ID?: string; BRAVE_API_KEY?: string; DEPOSIT_API_SECRET?: string; diff --git a/wrangler.toml b/wrangler.toml index caaa418..c5b1988 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -6,12 +6,11 @@ compatibility_date = "2024-01-01" binding = "AI" [vars] -SUMMARY_THRESHOLD = "20" -MAX_SUMMARIES_PER_USER = "3" -N8N_WEBHOOK_URL = "https://n8n.anvil.it.com" -DOMAIN_OWNER_ID = "821596605" -DEPOSIT_AGENT_ID = "asst_XMoVGU7ZwRpUPI6PHGvRNm8E" -DEPOSIT_ADMIN_ID = "821596605" +SUMMARY_THRESHOLD = "20" # ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ์ฃผ๊ธฐ (๋ฉ”์‹œ์ง€ ์ˆ˜) +MAX_SUMMARIES_PER_USER = "3" # ์œ ์ง€ํ•  ํ”„๋กœํ•„ ๋ฒ„์ „ ์ˆ˜ (์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ) +N8N_WEBHOOK_URL = "https://n8n.anvil.it.com" # n8n ์—ฐ๋™ (์„ ํƒ) +DOMAIN_OWNER_ID = "821596605" # ๋„๋ฉ”์ธ ๊ด€๋ฆฌ ๊ถŒํ•œ Telegram ID +DEPOSIT_ADMIN_ID = "821596605" # ์˜ˆ์น˜๊ธˆ ๊ด€๋ฆฌ ๊ถŒํ•œ Telegram ID [[d1_databases]] binding = "DB" @@ -27,3 +26,6 @@ database_id = "c285bb5b-888b-405d-b36f-475ae5aed20e" # - BOT_TOKEN: Telegram Bot Token # - WEBHOOK_SECRET: Webhook ๊ฒ€์ฆ์šฉ ์‹œํฌ๋ฆฟ # - OPENAI_API_KEY: OpenAI API ํ‚ค +# - NAMECHEAP_API_KEY: namecheap-api ๋ž˜ํผ ์ธ์ฆ ํ‚ค (๋„๋ฉ”์ธ ์ถ”์ฒœ์šฉ) +# - BRAVE_API_KEY: Brave Search API ํ‚ค +# - DEPOSIT_API_SECRET: Deposit API ์ธ์ฆ ํ‚ค (namecheap-api ์—ฐ๋™)