import { Body, Controller, Delete, Get, Headers, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger'; import { JwtService } from '@nestjs/jwt'; import { firstValueFrom } from 'rxjs'; import { CoreGrpcService } from '../core-grpc.service'; import { CreateBotDto, SetBotWebAppDto, SubmitBotCallbackDto, SubmitBotMessageDto, UpdateBotDto, UpdateBotProfileDto, ValidateWebAppInitDataDto } from '../dto/bot.dto'; import { getAuthorizedUserId } from '../document-access'; @ApiTags('BotFather') @ApiBearerAuth() @Controller('bots') export class BotController { constructor( private readonly core: CoreGrpcService, private readonly jwt: JwtService ) {} private async auth(authorization?: string) { return getAuthorizedUserId(this.jwt, this.core, authorization); } @Get() @ApiOperation({ summary: 'Список моих ботов', description: 'Возвращает Telegram-ботов текущего пользователя.' }) listMyBots(@Headers('authorization') authorization?: string) { return this.auth(authorization).then((userId) => firstValueFrom(this.core.bot.ListMyBots({ ownerId: userId }))); } @Post() @ApiOperation({ summary: 'Создать бота', description: 'Регистрирует нового бота и возвращает токен Bot API.' }) @ApiBody({ type: CreateBotDto }) createBot(@Headers('authorization') authorization: string | undefined, @Body() dto: CreateBotDto) { return this.auth(authorization).then((ownerId) => firstValueFrom(this.core.bot.CreateBot({ ownerId, name: dto.name, username: dto.username })) ); } @Get('by-username/:botRef/messages') @ApiOperation({ summary: 'История чата с ботом', description: 'Возвращает сообщения пользователя с ботом в хронологическом порядке.' }) listBotMessages(@Headers('authorization') authorization: string | undefined, @Param('botRef') botRef: string, @Query('roomId') roomId?: string) { return this.auth(authorization).then((userId) => firstValueFrom(this.core.bot.ListBotChatMessages({ userId, botRef, roomId })) ); } @Post('by-username/:botRef/messages') @ApiOperation({ summary: 'Написать боту', description: 'Публикует inbound-событие в RabbitMQ для доставки боту через webhook или getUpdates.' }) @ApiBody({ type: SubmitBotMessageDto }) submitMessage( @Headers('authorization') authorization: string | undefined, @Param('botRef') botRef: string, @Body() dto: SubmitBotMessageDto ) { return this.auth(authorization).then((senderUserId) => firstValueFrom(this.core.bot.SubmitBotInboundMessage({ senderUserId, botRef, text: dto.text, roomId: dto.roomId })) ); } @Post('by-username/:botRef/callback') @ApiOperation({ summary: 'Нажатие inline-кнопки', description: 'Публикует callback_query Update для webhook или getUpdates.' }) @ApiBody({ type: SubmitBotCallbackDto }) submitCallback( @Headers('authorization') authorization: string | undefined, @Param('botRef') botRef: string, @Body() dto: SubmitBotCallbackDto ) { return this.auth(authorization).then((senderUserId) => firstValueFrom( this.core.bot.SubmitBotCallbackQuery({ senderUserId, botRef, messageId: dto.messageId, callbackData: dto.callbackData }) ) ); } @Post('web-app/validate') @ApiOperation({ summary: 'Проверить initData Mini App', description: 'Валидирует Telegram Web App initData по HMAC-SHA256, как в официальном Bot API.' }) @ApiBody({ type: ValidateWebAppInitDataDto }) validateWebApp(@Body() dto: ValidateWebAppInitDataDto) { return firstValueFrom(this.core.bot.ValidateWebAppInitData(dto)); } @Get(':botId') @ApiOperation({ summary: 'Получить бота', description: 'Возвращает настройки бота, принадлежащего пользователю.' }) getBot(@Headers('authorization') authorization: string | undefined, @Param('botId') botId: string) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.GetBot({ requesterId, botId, isSuperAdmin: false })) ); } @Patch(':botId') @ApiOperation({ summary: 'Обновить бота', description: 'Изменяет имя или username бота.' }) @ApiBody({ type: UpdateBotDto }) updateBot( @Headers('authorization') authorization: string | undefined, @Param('botId') botId: string, @Body() dto: UpdateBotDto ) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.UpdateBot({ requesterId, botId, ...dto, isSuperAdmin: false })) ); } @Delete(':botId') @ApiOperation({ summary: 'Удалить бота', description: 'Удаляет бота и все связанные чаты/сообщения.' }) deleteBot(@Headers('authorization') authorization: string | undefined, @Param('botId') botId: string) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.DeleteBot({ requesterId, botId, isSuperAdmin: false })) ); } @Post(':botId/revoke-token') @ApiOperation({ summary: 'Перевыпустить токен', description: 'Инвалидирует старый токен и возвращает новый.' }) revokeToken(@Headers('authorization') authorization: string | undefined, @Param('botId') botId: string) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.RevokeBotToken({ requesterId, botId, isSuperAdmin: false })) ); } @Patch(':botId/profile') @ApiOperation({ summary: 'Профиль бота', description: 'Обновляет description, aboutText, botPicUrl и глобальную кнопку меню (Web App).' }) @ApiBody({ type: UpdateBotProfileDto }) updateBotProfile( @Headers('authorization') authorization: string | undefined, @Param('botId') botId: string, @Body() dto: UpdateBotProfileDto ) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.UpdateBotProfile({ requesterId, botId, ...dto, isSuperAdmin: false })) ); } @Patch(':botId/web-app') @ApiOperation({ summary: 'Настроить Mini App', description: 'Привязывает URL Web App к боту.' }) @ApiBody({ type: SetBotWebAppDto }) setWebApp( @Headers('authorization') authorization: string | undefined, @Param('botId') botId: string, @Body() dto: SetBotWebAppDto ) { return this.auth(authorization).then((requesterId) => firstValueFrom(this.core.bot.SetBotWebApp({ requesterId, botId, webAppUrl: dto.webAppUrl, isSuperAdmin: false })) ); } }