import type { Bot } from 'grammy' import type { BotContext } from '../types.js' import { getUnprocessedEvents, markEventProcessed, getPointById, getActiveQuestForUser, getActiveDayForQuest, } from '../../api/_lib/db.js' import { aiService } from './ai.service.js' import { generateNextPoint, generatePointContent } from './quest.service.js' import { t, getLang } from '../i18n/index.js' let processorInterval: ReturnType | null = null export function startEventProcessor(bot: Bot) { // Poll every 3 seconds for unprocessed events processorInterval = setInterval(async () => { try { await processEvents(bot) } catch (error) { console.error('Event processor error:', error) } }, 3000) console.log('Event processor started') } export function stopEventProcessor() { if (processorInterval) { clearInterval(processorInterval) processorInterval = null } } async function processEvents(bot: Bot) { const events = await getUnprocessedEvents() for (const event of events) { try { await processEvent(bot, event) await markEventProcessed(event.id) } catch (error) { console.error(`Failed to process event ${event.id}:`, error) // Still mark as processed to avoid infinite retry await markEventProcessed(event.id) } } } interface QuestEvent { id: string user_id: string point_id: string | null event_type: string payload: Record | null } async function processEvent(bot: Bot, event: QuestEvent) { // Look up the user to get their telegram_id for sending messages const { pool } = await import('../../api/_lib/db.js') const { rows: users } = await pool.query('SELECT * FROM users WHERE id = $1', [event.user_id]) const user = users[0] if (!user) return const lang = getLang(user.language_code) const quest = await getActiveQuestForUser(user.id) if (!quest) return const point = event.point_id ? await getPointById(event.point_id) : null switch (event.event_type) { case 'arrived': { if (point && !point.content_text) { // Generate full content for the point const content = await generatePointContent(point.id, { city: quest.city_name, pointTitle: point.title, dayNumber: 1, // TODO: get actual day number companions: quest.companions, pace: quest.pace, lang, }) if (content) { // Send a reaction to the chat const reaction = await aiService.generateReaction({ eventType: 'arrived', pointTitle: point.title, city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { await bot.api.sendMessage(user.telegram_id, reaction) } } } break } case 'point_completed': { // Generate next point const day = await getActiveDayForQuest(quest.id) if (!day) break await generateNextPoint(quest.id, { city: quest.city_name, dayNumber: day.day_number, totalDays: quest.number_of_days, dayTheme: day.theme || '', pace: quest.pace, companions: quest.companions, userComment: quest.user_comment, lang, }) const reaction = await aiService.generateReaction({ eventType: 'point_completed', pointTitle: point?.title, city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { const appUrl = process.env.APP_URL || 'https://guidly.example.com' const isHttps = appUrl.startsWith('https://') const msgText = isHttps ? reaction : reaction + `\n\nšŸ”— ${appUrl}` await bot.api.sendMessage(user.telegram_id, msgText, isHttps ? { reply_markup: { inline_keyboard: [[ { text: t(lang, 'open_quest'), web_app: { url: appUrl } } ]], }, } : {}) } break } case 'skipped': { const reaction = await aiService.generateReaction({ eventType: 'skipped', pointTitle: point?.title, city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { await bot.api.sendMessage(user.telegram_id, reaction) } // Generate next point const day = await getActiveDayForQuest(quest.id) if (day) { await generateNextPoint(quest.id, { city: quest.city_name, dayNumber: day.day_number, totalDays: quest.number_of_days, dayTheme: day.theme || '', pace: quest.pace, companions: quest.companions, userComment: quest.user_comment, lang, }) } break } case 'finished_today': { const reaction = await aiService.generateReaction({ eventType: 'finished_today', city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { await bot.api.sendMessage(user.telegram_id, reaction) } break } case 'day_completed': { const reaction = await aiService.generateReaction({ eventType: 'day_completed', city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { await bot.api.sendMessage(user.telegram_id, reaction) } break } case 'quest_completed': { const reaction = await aiService.generateReaction({ eventType: 'quest_completed', city: quest.city_name, lang, companions: quest.companions, pace: quest.pace, }) if (reaction) { await bot.api.sendMessage(user.telegram_id, reaction) } break } } }