update
This commit is contained in:
@@ -204,7 +204,7 @@ export class BotApiService {
|
||||
payload: this.serializer.buildStoredPayload(parsedMarkup)
|
||||
});
|
||||
|
||||
await this.publishBotMessage(context.recipient.id, bot as ValidatedBot, context.chat, message, parsedMarkup.internal);
|
||||
await this.publishBotMessage(context.recipient?.id ?? null, bot as ValidatedBot, context.chat, message, parsedMarkup.internal);
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -247,14 +247,16 @@ export class BotApiService {
|
||||
};
|
||||
}
|
||||
|
||||
const chat = roomId
|
||||
? await this.resolveSharedFamilyBotChat(bot.id, userId, roomId)
|
||||
const privateChat = roomId
|
||||
? await this.resolvePrivateBotChatForRoom(bot.id, userId, roomId)
|
||||
: await this.prisma.botChat.findUnique({
|
||||
where: { botId_recipientUserId: { botId: bot.id, recipientUserId: userId } },
|
||||
include: { menuButton: true }
|
||||
});
|
||||
|
||||
const composer = this.resolveComposerForBot(bot, chat?.menuButton?.menuButton ?? null);
|
||||
const groupChat = roomId ? await this.ensureRoomBotChat(bot.id, roomId) : null;
|
||||
|
||||
const composer = this.resolveComposerForBot(bot, privateChat?.menuButton?.menuButton ?? null);
|
||||
const emptyResponse = {
|
||||
messages: [] as Array<Record<string, unknown>>,
|
||||
botUsername: bot.username,
|
||||
@@ -266,25 +268,31 @@ export class BotApiService {
|
||||
manageWebAppUrl: buildBotManageWebAppUrl(bot.id)
|
||||
};
|
||||
|
||||
if (!chat) {
|
||||
if (!privateChat && !groupChat) {
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
const chatIds = [privateChat?.id, groupChat?.id].filter(Boolean) as string[];
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
this.prisma.botMessage.findMany({
|
||||
where: { botChatId: chat.id },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
where: { botChatId: { in: chatIds } },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
include: { botChat: { select: { chatType: true, roomId: true } } }
|
||||
}),
|
||||
this.prisma.botInboundMessage.findMany({
|
||||
where: { botChatId: chat.id },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
})
|
||||
privateChat
|
||||
? this.prisma.botInboundMessage.findMany({
|
||||
where: { botChatId: privateChat.id, senderUserId: userId },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
})
|
||||
: Promise.resolve([])
|
||||
]);
|
||||
|
||||
const messages = [
|
||||
...inbound.map((item) => ({
|
||||
id: `in-${item.id}`,
|
||||
direction: 'in' as const,
|
||||
scope: 'private' as const,
|
||||
text: item.text ?? '',
|
||||
messageType: 'text',
|
||||
messageId: item.telegramMsgId,
|
||||
@@ -296,9 +304,11 @@ export class BotApiService {
|
||||
...outbound.map((item) => {
|
||||
const payload = this.serializer.readPayload(item.payload);
|
||||
const replyMarkup = payload.telegramReplyMarkup ?? payload.replyMarkup;
|
||||
const isBroadcast = item.botChat.chatType === 'group';
|
||||
return {
|
||||
id: `out-${item.id}`,
|
||||
id: `${isBroadcast ? 'broadcast' : 'out'}-${item.id}`,
|
||||
direction: 'out' as const,
|
||||
scope: isBroadcast ? ('broadcast' as const) : ('private' as const),
|
||||
text: item.text ?? '',
|
||||
messageType: item.messageType ?? 'text',
|
||||
messageId: item.telegramMsgId,
|
||||
@@ -338,7 +348,7 @@ export class BotApiService {
|
||||
};
|
||||
}
|
||||
|
||||
private async resolveSharedFamilyBotChat(botId: string, viewerUserId: string, roomId: string) {
|
||||
private async resolvePrivateBotChatForRoom(botId: string, viewerUserId: string, roomId: string) {
|
||||
const room = await this.prisma.chatRoom.findUnique({
|
||||
where: { id: roomId },
|
||||
include: {
|
||||
@@ -352,14 +362,62 @@ export class BotApiService {
|
||||
if (!botMember || room.peerUserId !== botMember.userId) {
|
||||
return null;
|
||||
}
|
||||
const ownerUserId =
|
||||
room.createdById ??
|
||||
room.members.find((member) => !member.user.linkedBotId)?.userId ??
|
||||
viewerUserId;
|
||||
return this.prisma.botChat.findUnique({
|
||||
where: { botId_recipientUserId: { botId, recipientUserId: ownerUserId } },
|
||||
|
||||
const existing = await this.prisma.botChat.findUnique({
|
||||
where: { botId_recipientUserId: { botId, recipientUserId: viewerUserId } },
|
||||
include: { menuButton: true }
|
||||
});
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const telegramChatId = deriveTelegramChatId(`${botId}:${viewerUserId}`);
|
||||
try {
|
||||
return await this.prisma.botChat.create({
|
||||
data: {
|
||||
botId,
|
||||
recipientUserId: viewerUserId,
|
||||
telegramChatId,
|
||||
chatType: 'private'
|
||||
},
|
||||
include: { menuButton: true }
|
||||
});
|
||||
} catch {
|
||||
return this.prisma.botChat.findUnique({
|
||||
where: { botId_recipientUserId: { botId, recipientUserId: viewerUserId } },
|
||||
include: { menuButton: true }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async ensureRoomBotChat(botId: string, roomId: string) {
|
||||
const existing = await this.prisma.botChat.findUnique({
|
||||
where: { botId_roomId: { botId, roomId } }
|
||||
});
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const telegramChatId = deriveTelegramChatId(`${botId}:room:${roomId}`);
|
||||
try {
|
||||
return await this.prisma.botChat.create({
|
||||
data: {
|
||||
botId,
|
||||
roomId,
|
||||
telegramChatId,
|
||||
chatType: 'group',
|
||||
recipientUserId: null
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
const fallback = await this.prisma.botChat.findUnique({
|
||||
where: { botId_roomId: { botId, roomId } }
|
||||
});
|
||||
if (!fallback) {
|
||||
throw new Error('Не удалось создать групповой чат бота');
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private async setChatMenuButton(bot: ValidatedBot, payload: SetChatMenuButtonPayload) {
|
||||
@@ -444,7 +502,9 @@ export class BotApiService {
|
||||
},
|
||||
override
|
||||
);
|
||||
await this.publishMenuButtonUpdate(context.recipient.id, bot, composer);
|
||||
if (context.recipient) {
|
||||
await this.publishMenuButtonUpdate(context.recipient.id, bot, composer);
|
||||
}
|
||||
|
||||
return this.wrapOk(true);
|
||||
}
|
||||
@@ -485,7 +545,7 @@ export class BotApiService {
|
||||
payload: this.serializer.buildStoredPayload(parsedMarkup)
|
||||
});
|
||||
|
||||
await this.publishBotMessage(context.recipient.id, bot, context.chat, message, parsedMarkup.internal);
|
||||
await this.publishBotMessage(context.recipient?.id ?? null, bot, context.chat, message, parsedMarkup.internal);
|
||||
|
||||
return this.wrapOk(this.buildTelegramMessage(bot, context, message));
|
||||
}
|
||||
@@ -509,7 +569,7 @@ export class BotApiService {
|
||||
payload: this.serializer.buildStoredPayload(parsedMarkup)
|
||||
});
|
||||
|
||||
await this.publishBotMessage(context.recipient.id, bot, context.chat, message, parsedMarkup.internal, 'photo', photo);
|
||||
await this.publishBotMessage(context.recipient?.id ?? null, bot, context.chat, message, parsedMarkup.internal, 'photo', photo);
|
||||
|
||||
return this.wrapOk(this.buildTelegramMessage(bot, context, message));
|
||||
}
|
||||
@@ -533,7 +593,7 @@ export class BotApiService {
|
||||
payload: this.serializer.buildStoredPayload(parsedMarkup)
|
||||
});
|
||||
|
||||
await this.publishBotMessage(context.recipient.id, bot, context.chat, message, parsedMarkup.internal, 'document', document);
|
||||
await this.publishBotMessage(context.recipient?.id ?? null, bot, context.chat, message, parsedMarkup.internal, 'document', document);
|
||||
|
||||
return this.wrapOk(this.buildTelegramMessage(bot, context, message));
|
||||
}
|
||||
@@ -570,7 +630,7 @@ export class BotApiService {
|
||||
}
|
||||
});
|
||||
|
||||
await this.publishBotMessageEdited(located.recipient.id, bot, located.chat, updated, parsedMarkup.internal);
|
||||
await this.publishBotMessageEdited(located.recipient?.id ?? null, bot, located.chat, updated, parsedMarkup.internal);
|
||||
|
||||
return this.wrapOk(this.buildTelegramMessage(bot, located, updated));
|
||||
}
|
||||
@@ -599,7 +659,7 @@ export class BotApiService {
|
||||
}
|
||||
});
|
||||
|
||||
await this.publishBotMessageEdited(located.recipient.id, bot, located.chat, updated, parsedMarkup.internal);
|
||||
await this.publishBotMessageEdited(located.recipient?.id ?? null, bot, located.chat, updated, parsedMarkup.internal);
|
||||
|
||||
return this.wrapOk(this.buildTelegramMessage(bot, located, updated));
|
||||
}
|
||||
@@ -626,8 +686,8 @@ export class BotApiService {
|
||||
return this.wrapOk({
|
||||
id: Number(context.chat.telegramChatId),
|
||||
type: context.chat.chatType,
|
||||
first_name: context.recipient.displayName,
|
||||
...(context.recipient.username ? { username: context.recipient.username } : {}),
|
||||
first_name: context.recipient?.displayName ?? bot.name,
|
||||
...(context.recipient?.username ? { username: context.recipient.username } : {}),
|
||||
...(pinned ? { pinned_message: this.buildTelegramMessage(bot, context, pinned) } : {})
|
||||
});
|
||||
}
|
||||
@@ -676,7 +736,7 @@ export class BotApiService {
|
||||
data: pinned ? { isPinned: true, pinnedAt: new Date() } : { isPinned: false, pinnedAt: null }
|
||||
});
|
||||
|
||||
await this.publishBotMessagePinned(context.recipient.id, bot, context.chat, updated);
|
||||
await this.publishBotMessagePinned(context.recipient?.id ?? null, bot, context.chat, updated);
|
||||
return this.wrapOk(true);
|
||||
}
|
||||
|
||||
@@ -838,8 +898,34 @@ export class BotApiService {
|
||||
}
|
||||
|
||||
private async resolveChatContext(botId: string, chatIdRaw: string) {
|
||||
const numericChatId = chatIdRaw.match(/^\d+$/) ? BigInt(chatIdRaw) : null;
|
||||
if (numericChatId) {
|
||||
const chat = await this.prisma.botChat.findUnique({
|
||||
where: { botId_telegramChatId: { botId, telegramChatId: numericChatId } }
|
||||
});
|
||||
if (!chat) {
|
||||
return null;
|
||||
}
|
||||
if (chat.chatType === 'group') {
|
||||
return { recipient: null, chat };
|
||||
}
|
||||
if (!chat.recipientUserId) {
|
||||
return null;
|
||||
}
|
||||
const recipient = await this.prisma.user.findUnique({
|
||||
where: { id: chat.recipientUserId },
|
||||
select: { id: true, displayName: true, username: true, status: true, deletedAt: true }
|
||||
});
|
||||
if (!recipient || recipient.deletedAt || recipient.status !== 'ACTIVE') {
|
||||
return null;
|
||||
}
|
||||
return { recipient, chat };
|
||||
}
|
||||
|
||||
const recipient = await this.resolveRecipient(chatIdRaw, botId);
|
||||
if (!recipient) return null;
|
||||
if (!recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chat = await this.ensureBotChat(botId, recipient.id, chatIdRaw);
|
||||
return { recipient, chat };
|
||||
@@ -848,7 +934,7 @@ export class BotApiService {
|
||||
private buildTelegramMessage(
|
||||
bot: ValidatedBot,
|
||||
context: {
|
||||
recipient: { id: string; displayName: string; username: string | null };
|
||||
recipient: { id: string; displayName: string; username: string | null } | null;
|
||||
chat: { telegramChatId: bigint; chatType: string };
|
||||
},
|
||||
message: {
|
||||
@@ -869,16 +955,16 @@ export class BotApiService {
|
||||
chat: {
|
||||
telegramChatId: context.chat.telegramChatId,
|
||||
chatType: context.chat.chatType,
|
||||
recipientDisplayName: context.recipient.displayName,
|
||||
recipientUsername: context.recipient.username
|
||||
recipientDisplayName: context.recipient?.displayName ?? bot.name,
|
||||
recipientUsername: context.recipient?.username ?? null
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async publishBotMessage(
|
||||
userId: string,
|
||||
userId: string | null,
|
||||
bot: ValidatedBot,
|
||||
chat: { telegramChatId: bigint; chatType: string },
|
||||
chat: { telegramChatId: bigint; chatType: string; roomId?: string | null },
|
||||
message: {
|
||||
telegramMsgId: number;
|
||||
text?: string | null;
|
||||
@@ -900,16 +986,25 @@ export class BotApiService {
|
||||
messageType: message.messageType ?? mediaType ?? 'text',
|
||||
mediaUrl: message.mediaUrl ?? mediaUrl ?? null,
|
||||
replyMarkup: replyMarkup ?? payloadMarkup.replyMarkup ?? null,
|
||||
telegramReplyMarkup: payloadMarkup.telegramReplyMarkup ?? null
|
||||
telegramReplyMarkup: payloadMarkup.telegramReplyMarkup ?? null,
|
||||
scope: chat.chatType === 'group' ? 'broadcast' : 'private',
|
||||
roomId: chat.roomId ?? null
|
||||
};
|
||||
await this.notifications.publishRealtime(userId, 'bot_message', bot.name, message.text ?? '', payload);
|
||||
await this.publishToSharedBotRoomMembers(userId, bot, 'bot_message', message.text ?? '', payload);
|
||||
|
||||
if (chat.chatType === 'group' && chat.roomId) {
|
||||
await this.publishToRoomMembers(chat.roomId, bot, 'bot_message', message.text ?? '', payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
await this.notifications.publishRealtime(userId, 'bot_message', bot.name, message.text ?? '', payload);
|
||||
}
|
||||
}
|
||||
|
||||
private async publishBotMessagePinned(
|
||||
userId: string,
|
||||
userId: string | null,
|
||||
bot: ValidatedBot,
|
||||
chat: { telegramChatId: bigint; chatType: string },
|
||||
chat: { telegramChatId: bigint; chatType: string; roomId?: string | null },
|
||||
message: {
|
||||
telegramMsgId: number;
|
||||
isPinned?: boolean;
|
||||
@@ -922,16 +1017,25 @@ export class BotApiService {
|
||||
chatId: chat.telegramChatId.toString(),
|
||||
messageId: message.telegramMsgId,
|
||||
isPinned: Boolean(message.isPinned),
|
||||
pinnedAt: message.pinnedAt?.toISOString() ?? null
|
||||
pinnedAt: message.pinnedAt?.toISOString() ?? null,
|
||||
scope: chat.chatType === 'group' ? 'broadcast' : 'private',
|
||||
roomId: chat.roomId ?? null
|
||||
};
|
||||
await this.notifications.publishRealtime(userId, 'bot_message_pinned', bot.name, '', payload);
|
||||
await this.publishToSharedBotRoomMembers(userId, bot, 'bot_message_pinned', '', payload);
|
||||
|
||||
if (chat.chatType === 'group' && chat.roomId) {
|
||||
await this.publishToRoomMembers(chat.roomId, bot, 'bot_message_pinned', '', payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
await this.notifications.publishRealtime(userId, 'bot_message_pinned', bot.name, '', payload);
|
||||
}
|
||||
}
|
||||
|
||||
private async publishBotMessageEdited(
|
||||
userId: string,
|
||||
userId: string | null,
|
||||
bot: ValidatedBot,
|
||||
chat: { telegramChatId: bigint; chatType: string },
|
||||
chat: { telegramChatId: bigint; chatType: string; roomId?: string | null },
|
||||
message: {
|
||||
telegramMsgId: number;
|
||||
text?: string | null;
|
||||
@@ -953,41 +1057,46 @@ export class BotApiService {
|
||||
mediaUrl: message.mediaUrl ?? null,
|
||||
replyMarkup: replyMarkup ?? payloadMarkup.replyMarkup ?? null,
|
||||
telegramReplyMarkup: payloadMarkup.telegramReplyMarkup ?? null,
|
||||
editedAt: message.editedAt?.toISOString() ?? new Date().toISOString()
|
||||
editedAt: message.editedAt?.toISOString() ?? new Date().toISOString(),
|
||||
scope: chat.chatType === 'group' ? 'broadcast' : 'private',
|
||||
roomId: chat.roomId ?? null
|
||||
};
|
||||
await this.notifications.publishRealtime(userId, 'bot_message_edited', bot.name, message.text ?? '', payload);
|
||||
await this.publishToSharedBotRoomMembers(userId, bot, 'bot_message_edited', message.text ?? '', payload);
|
||||
|
||||
if (chat.chatType === 'group' && chat.roomId) {
|
||||
await this.publishToRoomMembers(chat.roomId, bot, 'bot_message_edited', message.text ?? '', payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
await this.notifications.publishRealtime(userId, 'bot_message_edited', bot.name, message.text ?? '', payload);
|
||||
}
|
||||
}
|
||||
|
||||
private async publishToSharedBotRoomMembers(
|
||||
ownerUserId: string,
|
||||
private async publishToRoomMembers(
|
||||
roomId: string,
|
||||
bot: ValidatedBot,
|
||||
eventType: string,
|
||||
message: string,
|
||||
payload: Record<string, unknown>
|
||||
) {
|
||||
const rooms = await this.prisma.chatRoom.findMany({
|
||||
where: {
|
||||
type: 'BOT',
|
||||
botUsername: bot.username,
|
||||
createdById: ownerUserId
|
||||
},
|
||||
const room = await this.prisma.chatRoom.findUnique({
|
||||
where: { id: roomId },
|
||||
include: {
|
||||
members: { include: { user: { select: { linkedBotId: true } } } }
|
||||
}
|
||||
});
|
||||
|
||||
const recipients = new Set<string>();
|
||||
for (const room of rooms) {
|
||||
for (const member of room.members) {
|
||||
if (member.userId !== ownerUserId && !member.user.linkedBotId) {
|
||||
recipients.add(member.userId);
|
||||
}
|
||||
}
|
||||
if (!room || room.type !== 'BOT') {
|
||||
return;
|
||||
}
|
||||
|
||||
const recipients = room.members
|
||||
.filter((member) => !member.user.linkedBotId)
|
||||
.map((member) => member.userId);
|
||||
|
||||
await Promise.all(
|
||||
[...recipients].map((recipientId) => this.notifications.publishRealtime(recipientId, eventType, bot.name, message, payload))
|
||||
recipients.map((recipientId) =>
|
||||
this.notifications.publishRealtime(recipientId, eventType, bot.name, message, payload)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user