diff --git a/package-lock.json b/package-lock.json index ecc3e52..2f543ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "telegram-summary-bot", "version": "1.0.0", "dependencies": { + "hono": "^4.11.7", "zod": "^4.3.5" }, "devDependencies": { @@ -2625,6 +2626,15 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", diff --git a/package.json b/package.json index ef0fe4c..d60d332 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "ai" ], "dependencies": { + "hono": "^4.11.7", "zod": "^4.3.5" } } diff --git a/src/index.ts b/src/index.ts index a824689..59be435 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,80 +10,75 @@ import { handleProvisionQueue, handleProvisionDLQ } from './server-provision'; import { timingSafeEqual } from './security'; import { createLogger } from './utils/logger'; import { notifyAdmin } from './services/notification'; +import { Hono } from 'hono'; const logger = createLogger('worker'); -export default { - // HTTP 요청 핸들러 - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); +// Hono app with Env type +const app = new Hono<{ Bindings: Env }>(); - // Webhook 설정 엔드포인트 - if (url.pathname === '/setup-webhook') { - if (!env.BOT_TOKEN) { - return Response.json({ error: 'BOT_TOKEN not configured' }, { status: 500 }); - } - if (!env.WEBHOOK_SECRET) { - return Response.json({ error: 'WEBHOOK_SECRET not configured' }, { status: 500 }); - } +// Health check (public - minimal info only) +app.get('/health', () => handleHealthCheck()); - // 인증: token + secret 검증 - const token = url.searchParams.get('token'); - const secret = url.searchParams.get('secret'); - if (!token || !timingSafeEqual(token, env.BOT_TOKEN)) { - return new Response('Unauthorized: Invalid or missing token', { status: 401 }); - } - if (!secret || !timingSafeEqual(secret, env.WEBHOOK_SECRET)) { - return new Response('Unauthorized: Invalid or missing secret', { status: 401 }); - } +// Setup webhook (with auth) +app.get('/setup-webhook', async (c) => { + const env = c.env; + if (!env.BOT_TOKEN) { + return c.json({ error: 'BOT_TOKEN not configured' }, 500); + } + if (!env.WEBHOOK_SECRET) { + return c.json({ error: 'WEBHOOK_SECRET not configured' }, 500); + } - const webhookUrl = `${url.origin}/webhook`; - const result = await setWebhook(env.BOT_TOKEN, webhookUrl, env.WEBHOOK_SECRET); - return Response.json(result); - } + // 인증: token + secret 검증 + const token = c.req.query('token'); + const secret = c.req.query('secret'); + if (!token || !timingSafeEqual(token, env.BOT_TOKEN)) { + return c.text('Unauthorized: Invalid or missing token', 401); + } + if (!secret || !timingSafeEqual(secret, env.WEBHOOK_SECRET)) { + return c.text('Unauthorized: Invalid or missing secret', 401); + } - // Webhook 정보 조회 - if (url.pathname === '/webhook-info') { - if (!env.BOT_TOKEN) { - return Response.json({ error: 'BOT_TOKEN not configured' }, { status: 500 }); - } - if (!env.WEBHOOK_SECRET) { - return Response.json({ error: 'WEBHOOK_SECRET not configured' }, { status: 500 }); - } + const webhookUrl = `${new URL(c.req.url).origin}/webhook`; + const result = await setWebhook(env.BOT_TOKEN, webhookUrl, env.WEBHOOK_SECRET); + return c.json(result); +}); - // 인증: token + secret 검증 - const token = url.searchParams.get('token'); - const secret = url.searchParams.get('secret'); - if (!token || !timingSafeEqual(token, env.BOT_TOKEN)) { - return new Response('Unauthorized: Invalid or missing token', { status: 401 }); - } - if (!secret || !timingSafeEqual(secret, env.WEBHOOK_SECRET)) { - return new Response('Unauthorized: Invalid or missing secret', { status: 401 }); - } +// Webhook info +app.get('/webhook-info', async (c) => { + const env = c.env; + if (!env.BOT_TOKEN) { + return c.json({ error: 'BOT_TOKEN not configured' }, 500); + } + if (!env.WEBHOOK_SECRET) { + return c.json({ error: 'WEBHOOK_SECRET not configured' }, 500); + } - const result = await getWebhookInfo(env.BOT_TOKEN); - return Response.json(result); - } + // 인증: token + secret 검증 + const token = c.req.query('token'); + const secret = c.req.query('secret'); + if (!token || !timingSafeEqual(token, env.BOT_TOKEN)) { + return c.text('Unauthorized: Invalid or missing token', 401); + } + if (!secret || !timingSafeEqual(secret, env.WEBHOOK_SECRET)) { + return c.text('Unauthorized: Invalid or missing secret', 401); + } - // 헬스 체크 (공개 - 최소 정보만) - if (url.pathname === '/health') { - return handleHealthCheck(); - } + const result = await getWebhookInfo(env.BOT_TOKEN); + return c.json(result); +}); - // API 엔드포인트 처리 - if (url.pathname.startsWith('/api/')) { - return handleApiRequest(request, env, url); - } +// API routes +app.all('/api/*', (c) => handleApiRequest(c.req.raw, c.env, new URL(c.req.url))); - // Telegram Webhook 처리 - if (url.pathname === '/webhook') { - return handleWebhook(request, env); - } +// Telegram Webhook +app.post('/webhook', (c) => handleWebhook(c.req.raw, c.env)); - // 루트 경로 - if (url.pathname === '/') { - return new Response(` -Telegram Rolling Summary Bot +// Root path +app.get('/', (c) => { + return c.text( + `Telegram Rolling Summary Bot Endpoints: GET /health - Health check @@ -91,14 +86,17 @@ Endpoints: GET /setup-webhook - Configure webhook POST /webhook - Telegram webhook (authenticated) -Documentation: https://github.com/your-repo - `.trim(), { - headers: { 'Content-Type': 'text/plain' }, - }); - } +Documentation: https://github.com/your-repo`, + 200 + ); +}); - return new Response('Not Found', { status: 404 }); - }, +// 404 handler +app.notFound((c) => c.text('Not Found', 404)); + +export default { + // HTTP 요청 핸들러 - Hono handles HTTP + fetch: app.fetch, // Email 핸들러 (SMS → 메일 → 파싱) async email(message: EmailMessage, env: Env): Promise {