mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 11:54:03 +00:00
267 lines
8.1 KiB
TypeScript
267 lines
8.1 KiB
TypeScript
import { webhookCallback } from 'grammy'
|
|
|
|
import {
|
|
createAnonymousFeedbackService,
|
|
createFinanceCommandService,
|
|
createHouseholdOnboardingService,
|
|
createHouseholdSetupService,
|
|
createReminderJobService
|
|
} from '@household/application'
|
|
import {
|
|
createDbAnonymousFeedbackRepository,
|
|
createDbFinanceRepository,
|
|
createDbHouseholdConfigurationRepository,
|
|
createDbReminderDispatchRepository
|
|
} from '@household/adapters-db'
|
|
import { configureLogger, getLogger } from '@household/observability'
|
|
|
|
import { registerAnonymousFeedback } from './anonymous-feedback'
|
|
import { createFinanceCommandsService } from './finance-commands'
|
|
import { createTelegramBot } from './bot'
|
|
import { getBotRuntimeConfig } from './config'
|
|
import { registerHouseholdSetupCommands } from './household-setup'
|
|
import { createOpenAiParserFallback } from './openai-parser-fallback'
|
|
import {
|
|
createPurchaseMessageRepository,
|
|
registerConfiguredPurchaseTopicIngestion
|
|
} from './purchase-topic-ingestion'
|
|
import { createReminderJobsHandler } from './reminder-jobs'
|
|
import { createSchedulerRequestAuthorizer } from './scheduler-auth'
|
|
import { createBotWebhookServer } from './server'
|
|
import { createMiniAppAuthHandler, createMiniAppJoinHandler } from './miniapp-auth'
|
|
import { createMiniAppDashboardHandler } from './miniapp-dashboard'
|
|
|
|
const runtime = getBotRuntimeConfig()
|
|
configureLogger({
|
|
level: runtime.logLevel,
|
|
service: '@household/bot'
|
|
})
|
|
|
|
const logger = getLogger('runtime')
|
|
const bot = createTelegramBot(runtime.telegramBotToken, getLogger('telegram'))
|
|
const webhookHandler = webhookCallback(bot, 'std/http')
|
|
|
|
const shutdownTasks: Array<() => Promise<void>> = []
|
|
const householdConfigurationRepositoryClient = runtime.databaseUrl
|
|
? createDbHouseholdConfigurationRepository(runtime.databaseUrl)
|
|
: null
|
|
const financeRepositoryClient =
|
|
runtime.financeCommandsEnabled || runtime.miniAppAuthEnabled
|
|
? createDbFinanceRepository(runtime.databaseUrl!, runtime.householdId!)
|
|
: null
|
|
const financeService = financeRepositoryClient
|
|
? createFinanceCommandService(financeRepositoryClient.repository)
|
|
: null
|
|
const householdOnboardingService = householdConfigurationRepositoryClient
|
|
? createHouseholdOnboardingService({
|
|
repository: householdConfigurationRepositoryClient.repository,
|
|
...(financeRepositoryClient
|
|
? {
|
|
getMemberByTelegramUserId: financeRepositoryClient.repository.getMemberByTelegramUserId
|
|
}
|
|
: {})
|
|
})
|
|
: null
|
|
const anonymousFeedbackRepositoryClient = runtime.anonymousFeedbackEnabled
|
|
? createDbAnonymousFeedbackRepository(runtime.databaseUrl!, runtime.householdId!)
|
|
: null
|
|
const anonymousFeedbackService = anonymousFeedbackRepositoryClient
|
|
? createAnonymousFeedbackService(anonymousFeedbackRepositoryClient.repository)
|
|
: null
|
|
|
|
if (financeRepositoryClient) {
|
|
shutdownTasks.push(financeRepositoryClient.close)
|
|
}
|
|
|
|
if (householdConfigurationRepositoryClient) {
|
|
shutdownTasks.push(householdConfigurationRepositoryClient.close)
|
|
}
|
|
|
|
if (anonymousFeedbackRepositoryClient) {
|
|
shutdownTasks.push(anonymousFeedbackRepositoryClient.close)
|
|
}
|
|
|
|
if (runtime.databaseUrl && householdConfigurationRepositoryClient) {
|
|
const purchaseRepositoryClient = createPurchaseMessageRepository(runtime.databaseUrl!)
|
|
shutdownTasks.push(purchaseRepositoryClient.close)
|
|
const llmFallback = createOpenAiParserFallback(runtime.openaiApiKey, runtime.parserModel)
|
|
|
|
registerConfiguredPurchaseTopicIngestion(
|
|
bot,
|
|
householdConfigurationRepositoryClient.repository,
|
|
purchaseRepositoryClient.repository,
|
|
{
|
|
...(llmFallback
|
|
? {
|
|
llmFallback
|
|
}
|
|
: {}),
|
|
logger: getLogger('purchase-ingestion')
|
|
}
|
|
)
|
|
} else {
|
|
logger.warn(
|
|
{
|
|
event: 'runtime.feature_disabled',
|
|
feature: 'purchase-topic-ingestion'
|
|
},
|
|
'Purchase topic ingestion is disabled. Set DATABASE_URL to enable Telegram topic lookups.'
|
|
)
|
|
}
|
|
|
|
if (runtime.financeCommandsEnabled) {
|
|
const financeCommands = createFinanceCommandsService(financeService!)
|
|
|
|
financeCommands.register(bot)
|
|
} else {
|
|
logger.warn(
|
|
{
|
|
event: 'runtime.feature_disabled',
|
|
feature: 'finance-commands'
|
|
},
|
|
'Finance commands are disabled. Set DATABASE_URL and HOUSEHOLD_ID to enable.'
|
|
)
|
|
}
|
|
|
|
if (householdConfigurationRepositoryClient) {
|
|
registerHouseholdSetupCommands({
|
|
bot,
|
|
householdSetupService: createHouseholdSetupService(
|
|
householdConfigurationRepositoryClient.repository
|
|
),
|
|
householdOnboardingService: householdOnboardingService!,
|
|
logger: getLogger('household-setup')
|
|
})
|
|
} else {
|
|
logger.warn(
|
|
{
|
|
event: 'runtime.feature_disabled',
|
|
feature: 'household-setup'
|
|
},
|
|
'Household setup commands are disabled. Set DATABASE_URL to enable.'
|
|
)
|
|
}
|
|
|
|
const reminderJobs = runtime.reminderJobsEnabled
|
|
? (() => {
|
|
const reminderRepositoryClient = createDbReminderDispatchRepository(runtime.databaseUrl!)
|
|
const reminderService = createReminderJobService(reminderRepositoryClient.repository)
|
|
|
|
shutdownTasks.push(reminderRepositoryClient.close)
|
|
|
|
return createReminderJobsHandler({
|
|
householdId: runtime.householdId!,
|
|
reminderService,
|
|
logger: getLogger('scheduler')
|
|
})
|
|
})()
|
|
: null
|
|
|
|
if (!runtime.reminderJobsEnabled) {
|
|
logger.warn(
|
|
{
|
|
event: 'runtime.feature_disabled',
|
|
feature: 'reminder-jobs'
|
|
},
|
|
'Reminder jobs are disabled. Set DATABASE_URL, HOUSEHOLD_ID, and either SCHEDULER_SHARED_SECRET or SCHEDULER_OIDC_ALLOWED_EMAILS to enable.'
|
|
)
|
|
}
|
|
|
|
if (anonymousFeedbackService) {
|
|
registerAnonymousFeedback({
|
|
bot,
|
|
anonymousFeedbackService,
|
|
householdChatId: runtime.telegramHouseholdChatId!,
|
|
feedbackTopicId: runtime.telegramFeedbackTopicId!,
|
|
logger: getLogger('anonymous-feedback')
|
|
})
|
|
} else {
|
|
logger.warn(
|
|
{
|
|
event: 'runtime.feature_disabled',
|
|
feature: 'anonymous-feedback'
|
|
},
|
|
'Anonymous feedback is disabled. Set DATABASE_URL, HOUSEHOLD_ID, TELEGRAM_HOUSEHOLD_CHAT_ID, and TELEGRAM_FEEDBACK_TOPIC_ID to enable.'
|
|
)
|
|
}
|
|
|
|
const server = createBotWebhookServer({
|
|
webhookPath: runtime.telegramWebhookPath,
|
|
webhookSecret: runtime.telegramWebhookSecret,
|
|
webhookHandler,
|
|
miniAppAuth: householdOnboardingService
|
|
? createMiniAppAuthHandler({
|
|
allowedOrigins: runtime.miniAppAllowedOrigins,
|
|
botToken: runtime.telegramBotToken,
|
|
onboardingService: householdOnboardingService,
|
|
logger: getLogger('miniapp-auth')
|
|
})
|
|
: undefined,
|
|
miniAppJoin: householdOnboardingService
|
|
? createMiniAppJoinHandler({
|
|
allowedOrigins: runtime.miniAppAllowedOrigins,
|
|
botToken: runtime.telegramBotToken,
|
|
onboardingService: householdOnboardingService,
|
|
logger: getLogger('miniapp-auth')
|
|
})
|
|
: undefined,
|
|
miniAppDashboard: financeService
|
|
? createMiniAppDashboardHandler({
|
|
allowedOrigins: runtime.miniAppAllowedOrigins,
|
|
botToken: runtime.telegramBotToken,
|
|
financeService,
|
|
onboardingService: householdOnboardingService!,
|
|
logger: getLogger('miniapp-dashboard')
|
|
})
|
|
: undefined,
|
|
scheduler:
|
|
reminderJobs && runtime.schedulerSharedSecret
|
|
? {
|
|
authorize: createSchedulerRequestAuthorizer({
|
|
sharedSecret: runtime.schedulerSharedSecret,
|
|
oidcAllowedEmails: runtime.schedulerOidcAllowedEmails
|
|
}).authorize,
|
|
handler: reminderJobs.handle
|
|
}
|
|
: reminderJobs
|
|
? {
|
|
authorize: createSchedulerRequestAuthorizer({
|
|
oidcAllowedEmails: runtime.schedulerOidcAllowedEmails
|
|
}).authorize,
|
|
handler: reminderJobs.handle
|
|
}
|
|
: undefined
|
|
})
|
|
|
|
if (import.meta.main) {
|
|
Bun.serve({
|
|
port: runtime.port,
|
|
fetch: server.fetch
|
|
})
|
|
|
|
logger.info(
|
|
{
|
|
event: 'runtime.started',
|
|
port: runtime.port,
|
|
webhookPath: runtime.telegramWebhookPath
|
|
},
|
|
'Bot webhook server started'
|
|
)
|
|
|
|
process.on('SIGTERM', () => {
|
|
logger.info(
|
|
{
|
|
event: 'runtime.shutdown',
|
|
signal: 'SIGTERM'
|
|
},
|
|
'Bot shutdown requested'
|
|
)
|
|
|
|
for (const close of shutdownTasks) {
|
|
void close()
|
|
}
|
|
})
|
|
}
|
|
|
|
export { server }
|