| import { verifyHfIncomingRequest } from '../../lib/hf-bridge.js'; |
| import { getConfig } from '../../lib/config.js'; |
| import { upsertTranscriptPayload } from '../../services/archive/upsert-transcript-payload.js'; |
| import { normalizeVideoByKey } from '../../services/archive/normalize-video.js'; |
|
|
| function setCors(res) { |
| res.setHeader('Access-Control-Allow-Credentials', 'true'); |
| res.setHeader('Access-Control-Allow-Origin', '*'); |
| res.setHeader('Access-Control-Allow-Methods', 'POST,OPTIONS'); |
| res.setHeader( |
| 'Access-Control-Allow-Headers', |
| 'Content-Type, X-HF-Shared-Secret, X-HF-Timestamp, X-HF-Signature, X-Telegram-Bot-Api-Secret-Token' |
| ); |
| } |
|
|
| function normalizeBaseUrl(baseUrl) { |
| return String(baseUrl || '').trim().replace(/\/+$/, ''); |
| } |
|
|
| async function forwardTelegramUpdateToHf({ cfg, update }) { |
| const secret = String(cfg.telegramWebhookSecret || '').trim(); |
| const hfBase = normalizeBaseUrl(cfg.hfBackendBaseUrl); |
| if (!hfBase) { |
| const error = new Error('HF_BACKEND_BASE_URL is not configured'); |
| error.status = 503; |
| throw error; |
| } |
|
|
| const targetUrl = `${hfBase}/api/telegram/webhook/${encodeURIComponent(secret)}`; |
| const controller = new AbortController(); |
| const timeout = setTimeout(() => controller.abort(), 8000); |
|
|
| try { |
| const response = await fetch(targetUrl, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'X-Telegram-Bot-Api-Secret-Token': secret, |
| }, |
| body: JSON.stringify(update || {}), |
| signal: controller.signal, |
| }); |
|
|
| if (!response.ok) { |
| const bodyText = await response.text().catch(() => ''); |
| const error = new Error( |
| `HF telegram worker rejected webhook update (${response.status})${bodyText ? `: ${bodyText.slice(0, 220)}` : ''}` |
| ); |
| error.status = 502; |
| throw error; |
| } |
| } catch (error) { |
| if (error?.name === 'AbortError') { |
| const timeoutError = new Error('HF telegram worker timeout'); |
| timeoutError.status = 502; |
| throw timeoutError; |
| } |
| throw error; |
| } finally { |
| clearTimeout(timeout); |
| } |
| } |
|
|
| export default async function handler(req, res) { |
| setCors(res); |
|
|
| if (req.method === 'OPTIONS') { |
| return res.status(200).end(); |
| } |
|
|
| if (req.method !== 'POST') { |
| return res.status(405).json({ message: 'Method not allowed' }); |
| } |
|
|
| const cfg = getConfig(); |
| const telegramHeaderSecret = String(req.headers['x-telegram-bot-api-secret-token'] || '').trim(); |
| if (telegramHeaderSecret) { |
| const expectedSecret = String(cfg.telegramWebhookSecret || '').trim(); |
| if (!expectedSecret) { |
| return res.status(503).json({ |
| message: 'TELEGRAM_WEBHOOK_SECRET is not configured', |
| }); |
| } |
|
|
| if (telegramHeaderSecret !== expectedSecret) { |
| return res.status(401).json({ |
| message: 'Unauthorized Telegram webhook request', |
| }); |
| } |
|
|
| try { |
| await forwardTelegramUpdateToHf({ |
| cfg, |
| update: req.body && typeof req.body === 'object' ? req.body : {}, |
| }); |
| return res.status(200).json({ ok: true, accepted: true }); |
| } catch (error) { |
| return res.status(Number(error?.status) || 502).json({ |
| message: error?.message || 'HF telegram worker unavailable', |
| }); |
| } |
| } |
|
|
| const auth = verifyHfIncomingRequest({ |
| headers: req.headers || {}, |
| body: req.body || {}, |
| }); |
| if (!auth.ok) { |
| return res.status(401).json({ |
| message: 'Unauthorized HF bridge request', |
| reason: auth.reason, |
| }); |
| } |
|
|
| try { |
| const body = req.body || {}; |
| if (String(body.action || '').trim() === 'telegram_proxy') { |
| const method = String(body.method || '').trim(); |
| const payload = body.payload && typeof body.payload === 'object' ? body.payload : null; |
| if (!method || !payload) { |
| return res.status(400).json({ |
| message: 'telegram_proxy requires { method, payload }', |
| }); |
| } |
|
|
| const botToken = String(cfg.telegramBotToken || '').trim(); |
| if (!botToken) { |
| return res.status(503).json({ |
| message: 'TELEGRAM_BOT_TOKEN is not configured on bridge server', |
| }); |
| } |
|
|
| const tgResponse = await fetch(`https://api.telegram.org/bot${botToken}/${method}`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify(payload), |
| }); |
| const tgJson = await tgResponse.json().catch(() => ({})); |
| if (!tgResponse.ok || tgJson?.ok === false) { |
| return res.status(502).json({ |
| message: 'Telegram proxy call failed', |
| telegram_error: tgJson?.description || `status ${tgResponse.status}`, |
| }); |
| } |
|
|
| return res.status(200).json({ |
| ok: true, |
| result: tgJson?.result || null, |
| }); |
| } |
|
|
| if (String(body.action || '').trim() === 'telegram_proxy_document') { |
| const payload = body.payload && typeof body.payload === 'object' ? body.payload : null; |
| if (!payload) { |
| return res.status(400).json({ |
| message: 'telegram_proxy_document requires { payload }', |
| }); |
| } |
|
|
| const chatId = String(payload.chat_id || '').trim(); |
| const filename = String(payload.filename || 'artifact.md').trim(); |
| const caption = String(payload.caption || '').trim(); |
| const content = String(payload.content || ''); |
| if (!chatId) { |
| return res.status(400).json({ |
| message: 'telegram_proxy_document payload.chat_id is required', |
| }); |
| } |
|
|
| const botToken = String(cfg.telegramBotToken || '').trim(); |
| if (!botToken) { |
| return res.status(503).json({ |
| message: 'TELEGRAM_BOT_TOKEN is not configured on bridge server', |
| }); |
| } |
|
|
| const formData = new FormData(); |
| formData.append('chat_id', chatId); |
| if (caption) formData.append('caption', caption); |
| formData.append( |
| 'document', |
| new Blob([content], { type: 'text/markdown;charset=utf-8' }), |
| filename |
| ); |
|
|
| const tgResponse = await fetch(`https://api.telegram.org/bot${botToken}/sendDocument`, { |
| method: 'POST', |
| body: formData, |
| }); |
| const tgJson = await tgResponse.json().catch(() => ({})); |
| if (!tgResponse.ok || tgJson?.ok === false) { |
| return res.status(502).json({ |
| message: 'Telegram proxy document call failed', |
| telegram_error: tgJson?.description || `status ${tgResponse.status}`, |
| }); |
| } |
|
|
| return res.status(200).json({ |
| ok: true, |
| result: tgJson?.result || null, |
| }); |
| } |
|
|
| const sourceUrl = String(body.source_url || '').trim(); |
| const transcript = String(body.transcript || '').trim(); |
| const sourceHash = String(body.source_hash || '').trim() || null; |
| const transcriptSource = String(body.transcript_source || body.source || '').trim() || 'vercel-transcribe'; |
| const userId = String(body.user_id || '').trim() || null; |
| const targetLanguage = String(body.target_language || '').trim() || null; |
| const runPostprocess = body.run_postprocess !== false; |
| const waitForCompletion = body.wait_for_completion === true; |
|
|
| const upsert = await upsertTranscriptPayload({ |
| sourceUrl, |
| transcript, |
| sourceHash, |
| transcriptSource, |
| userId, |
| }); |
|
|
| if (!runPostprocess) { |
| return res.status(200).json({ |
| accepted: true, |
| video_key: upsert.video_key, |
| postprocess: 'skipped', |
| }); |
| } |
|
|
| if (waitForCompletion) { |
| const normalized = await normalizeVideoByKey(upsert.video_key, { targetLanguage }); |
| return res.status(200).json({ |
| accepted: true, |
| video_key: upsert.video_key, |
| postprocess: 'completed', |
| normalized, |
| }); |
| } |
|
|
| |
| |
| |
| normalizeVideoByKey(upsert.video_key, { targetLanguage }).catch((error) => { |
| console.error(`[hf-post-transcribe] normalize failed for ${upsert.video_key}:`, error); |
| }); |
|
|
| return res.status(202).json({ |
| accepted: true, |
| video_key: upsert.video_key, |
| postprocess: 'queued', |
| }); |
| } catch (error) { |
| return res.status(Number(error?.status) || 500).json({ |
| message: error?.message || 'HF post-transcribe processing failed', |
| }); |
| } |
| } |
|
|