multica/apps/gateway/telegram/telegram.controller.ts

105 lines
3.2 KiB
TypeScript

/**
* Telegram controller.
*
* - POST /telegram/webhook — Receives webhook requests from Telegram Bot API
* - POST /telegram/connect-code — Creates a short code for QR deep link flow
*/
import { Body, Controller, HttpException, HttpStatus, Inject, Logger, Post, Req, Res, Headers } from "@nestjs/common";
import { TelegramService } from "./telegram.service.js";
import type { ConnectionInfo } from "@multica/store/connection";
// Minimal Express types for webhook handling
interface ExpressRequest {
body: unknown;
header: (name: string) => string | undefined;
}
interface ExpressResponse {
status: (code: number) => ExpressResponse;
json: (data: unknown) => void;
headersSent: boolean;
}
@Controller("telegram")
export class TelegramController {
private readonly logger = new Logger(TelegramController.name);
constructor(@Inject(TelegramService) private readonly telegramService: TelegramService) {}
@Post("connect-code")
async createConnectCode(
@Body() body: {
gateway: string;
hubId: string;
agentId: string;
conversationId: string;
token: string;
expires: number;
},
): Promise<{ code: string; botUsername: string }> {
if (!this.telegramService.isConfigured()) {
throw new HttpException("Telegram bot not configured", HttpStatus.SERVICE_UNAVAILABLE);
}
const botUsername = this.telegramService.getBotUsername();
if (!botUsername) {
throw new HttpException("Bot username not available", HttpStatus.INTERNAL_SERVER_ERROR);
}
const connectionInfo: ConnectionInfo = {
type: "multica-connect",
gateway: body.gateway,
hubId: body.hubId,
agentId: body.agentId,
conversationId: body.conversationId,
token: body.token,
expires: body.expires,
};
const code = this.telegramService.createConnectCode(connectionInfo);
this.logger.debug(`Created connect code: ${code}`);
return { code, botUsername };
}
@Post("webhook")
async handleWebhook(
@Req() req: ExpressRequest,
@Res() res: ExpressResponse,
@Headers("x-telegram-bot-api-secret-token") secretToken?: string
): Promise<void> {
// Check if Telegram is configured
if (!this.telegramService.isConfigured()) {
this.logger.warn("Telegram webhook called but bot not configured");
res.status(503).json({ error: "Telegram not configured" });
return;
}
// Validate secret token if configured
const expectedToken = process.env["TELEGRAM_WEBHOOK_SECRET_TOKEN"];
if (expectedToken && secretToken !== expectedToken) {
this.logger.warn("Invalid Telegram webhook secret token");
res.status(401).json({ error: "Unauthorized" });
return;
}
// Get grammY webhook callback
const callback = this.telegramService.getWebhookCallback();
if (!callback) {
res.status(503).json({ error: "Telegram not configured" });
return;
}
// Let grammY handle the request
try {
await callback(req, res);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.logger.error(`Telegram webhook error: ${message}`);
if (!res.headersSent) {
res.status(500).json({ error: "Internal server error" });
}
}
}
}