feat: telegram bot (grammy webhook) — full parity channel
- pairing (admin code -> chat link), bundling of forwarded images via drafts+quiet-window sweeper - recipe pick via inline keyboard, job (origin=telegram) creation, one summary reply with result files + links - compressed-photo detection/flag; /start /help /rezepte /status - webhook route (secret token), admin pairing-code + links endpoints, AdminApp telegram section - worker notifies chat on completion; migration 002 adds jobs.telegram_chat_id; setupTelegram on init
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { query } from '../../../../lib/db';
|
||||
|
||||
export const prerender = false;
|
||||
const json = (b: unknown, s = 200) =>
|
||||
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const links = await query(`SELECT l.chat_id, l.active, l.linked_at, u.display_name AS user_name
|
||||
FROM telegram_links l LEFT JOIN users u ON u.id=l.user_id ORDER BY l.linked_at DESC`);
|
||||
return json({ links });
|
||||
};
|
||||
|
||||
export const DELETE: APIRoute = async ({ request }) => {
|
||||
const b = await request.json().catch(() => ({}));
|
||||
if (!b.chat_id) return json({ error: 'chat_id nötig.' }, 400);
|
||||
await query('DELETE FROM telegram_links WHERE chat_id=$1', [b.chat_id]);
|
||||
return json({ ok: true });
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { one } from '../../../../lib/db';
|
||||
|
||||
export const prerender = false;
|
||||
const json = (b: unknown, s = 200) =>
|
||||
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
|
||||
|
||||
// Erzeugt einen 6-stelligen Kopplungscode (15 Min gültig) für einen Nutzer.
|
||||
export const POST: APIRoute = async ({ request, locals }) => {
|
||||
const b = await request.json().catch(() => ({}));
|
||||
const userId = b.userId || locals.user?.uid;
|
||||
if (!userId) return json({ error: 'userId nötig.' }, 400);
|
||||
// 6-stelliger Code (Math.random ist hier ok — kein Krypto-Geheimnis, nur kurzlebig)
|
||||
const code = String(Math.floor(100000 + Math.random() * 900000));
|
||||
await one(`INSERT INTO telegram_pairing_codes (code, user_id, expires_at)
|
||||
VALUES ($1,$2, now() + interval '15 minutes')
|
||||
ON CONFLICT (code) DO UPDATE SET user_id=$2, expires_at=now()+interval '15 minutes', used_at=NULL`,
|
||||
[code, userId]);
|
||||
return json({ code, expires_minutes: 15 });
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { webhookCallback } from 'grammy';
|
||||
import { getBot, getWebhookSecret } from '../../../lib/telegram';
|
||||
|
||||
export const prerender = false;
|
||||
let handler: ((req: Request) => Promise<Response>) | null = null;
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const bot = await getBot();
|
||||
if (!bot) return new Response('Bot inaktiv', { status: 503 });
|
||||
if (!handler) {
|
||||
handler = webhookCallback(bot, 'std/http', { secretToken: getWebhookSecret() || undefined });
|
||||
}
|
||||
try {
|
||||
return await handler(request);
|
||||
} catch (e) {
|
||||
console.error('[telegram] webhook', e);
|
||||
return new Response('ok'); // Telegram nicht mit 5xx fluten
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user