From 3c63f108b2cb7c12f8954737849d6c0410af4d81 Mon Sep 17 00:00:00 2001 From: Kristina Date: Thu, 26 Sep 2024 18:47:13 +0400 Subject: [PATCH] Fix tg parsing error for inline actions (#6747) Signed-off-by: Kristina Fefelova --- .../pod-telegram-bot/src/telegraf/bot.ts | 49 +++++++++-------- .../pod-telegram-bot/src/worker.ts | 55 +++++++++++++++---- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/services/telegram-bot/pod-telegram-bot/src/telegraf/bot.ts b/services/telegram-bot/pod-telegram-bot/src/telegraf/bot.ts index c85bb6548f..fe45c6aaf4 100644 --- a/services/telegram-bot/pod-telegram-bot/src/telegraf/bot.ts +++ b/services/telegram-bot/pod-telegram-bot/src/telegraf/bot.ts @@ -20,7 +20,7 @@ import { htmlToMarkup, isEmptyMarkup, jsonToMarkup, MarkupNodeType } from '@hcen import { toHTML } from '@telegraf/entity' import { CallbackQuery, Message, Update } from 'telegraf/typings/core/types/typegram' import { translate } from '@hcengineering/platform' -import { WithId } from 'mongodb' +import { ObjectId, WithId } from 'mongodb' import config from '../config' import { PlatformWorker } from '../worker' @@ -29,13 +29,13 @@ import { toTelegramFileInfo } from '../utils' import { Command, defineCommands } from './commands' import { ChannelRecord, MessageRecord, TelegramFileInfo, UserRecord, WorkspaceInfo } from '../types' -function encodeChannelId (workspace: string, channelId: string): string { - return `${workspace}_${channelId}` +function encodeChannelId (channelId: string): string { + return `@${channelId}` } -function decodeChannelId (id: string): { workspace: string, channelId: string } { - const [workspace, channelId] = id.split('_') - return { workspace, channelId } +function decodeChannelId (id: string): string | undefined { + const [, channelId] = id.split('@') + return channelId } const getNextActionId = (workspace: string, page: number): string => `next_${workspace}_${page}` @@ -114,10 +114,9 @@ async function handleSelectChannel ( const userRecord = await worker.getUserRecord(id) if (userRecord === undefined) return ['', false] - const { workspace, channelId } = decodeChannelId(match) - - const channels = await worker.getChannels(userRecord.email, workspace) - const channel = channels.find((it) => it._id.toString() === channelId) + const channelId = decodeChannelId(match) + if (channelId === undefined || channelId === '') return ['', false] + const channel = await worker.getChannel(userRecord.email, new ObjectId(channelId)) if (channel === undefined) return ['', false] @@ -134,6 +133,14 @@ async function handleSelectChannel ( return [channel.name, await worker.sendMessage(channel, userMessage.message_id, text, file)] } +async function showNoChannelsMessage (ctx: Context, worker: PlatformWorker, workspace: string): Promise { + const ws = await worker.getWorkspaceInfo(workspace) + await ctx.editMessageText( + `No channels found in workspace ${ws?.name ?? workspace}.\nTo sync channels call /${Command.SyncAllChannels} or /${Command.SyncStarredChannels}`, + { parse_mode: 'HTML' } + ) +} + async function createSelectChannelKeyboard ( ctx: NarrowedContext, worker: PlatformWorker, @@ -141,6 +148,12 @@ async function createSelectChannelKeyboard ( workspace: string ): Promise { const channels = await worker.getChannels(userRecord.email, workspace) + + if (channels.length === 0) { + await showNoChannelsMessage(ctx, worker, workspace) + return + } + const hasNext = channels.length > channelsPerPage const pageChannels = getPageChannels(channels, 0) @@ -148,9 +161,7 @@ async function createSelectChannelKeyboard ( reply_parameters: { message_id: ctx.message.message_id }, ...Markup.inlineKeyboard( [ - ...pageChannels.map((channel) => - Markup.button.callback(channel.name, encodeChannelId(channel.workspace, channel._id.toString())) - ), + ...pageChannels.map((channel) => Markup.button.callback(channel.name, encodeChannelId(channel._id.toString()))), ...(hasNext ? [Markup.button.callback('Next>', getNextActionId(workspace, 0))] : []) ], { columns: 1 } @@ -283,7 +294,7 @@ export async function setUpBot (worker: PlatformWorker): Promise { + bot.action(/@.+/, async (ctx) => { const messageId = ctx.callbackQuery.message?.message_id if (messageId === undefined) return if (ctx.processingKeyboards.has(messageId)) return @@ -341,11 +352,7 @@ const editChannelKeyboard = async ( const channels = await worker.getChannels(userRecord.email, workspace) if (channels.length === 0) { - const ws = await worker.getWorkspaceInfo(workspace) - await ctx.editMessageText( - `No channels found in workspace ${ws?.name ?? workspace}.\nTo add channels call /${Command.SyncAllChannels} or /${Command.SyncStarredChannels}`, - { parse_mode: 'HTML' } - ) + await showNoChannelsMessage(ctx, worker, workspace) return } @@ -355,9 +362,7 @@ const editChannelKeyboard = async ( await ctx.editMessageReplyMarkup({ inline_keyboard: [ - ...pageChannels.map((channel) => [ - Markup.button.callback(channel.name, encodeChannelId(channel.workspace, channel._id.toString())) - ]), + ...pageChannels.map((channel) => [Markup.button.callback(channel.name, encodeChannelId(channel._id.toString()))]), [ ...(hasPrev ? [Markup.button.callback('', getNextActionId(workspace, page))] : []) diff --git a/services/telegram-bot/pod-telegram-bot/src/worker.ts b/services/telegram-bot/pod-telegram-bot/src/worker.ts index d5f60cf909..e4612cc379 100644 --- a/services/telegram-bot/pod-telegram-bot/src/worker.ts +++ b/services/telegram-bot/pod-telegram-bot/src/worker.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import type { Collection, WithId } from 'mongodb' +import type { Collection, ObjectId, WithId } from 'mongodb' import { MeasureContext, Ref, SortingOrder, systemAccountEmail } from '@hcengineering/core' import { InboxNotification } from '@hcengineering/notification' import { TelegramNotificationRequest } from '@hcengineering/telegram' @@ -44,9 +44,11 @@ const closeWorkspaceTimeout = 10 * 60 * 1000 // 10 minutes export class PlatformWorker { private readonly workspacesClients = new Map() private readonly closeWorkspaceTimeouts: Map = new Map() - private readonly intervalId: NodeJS.Timeout | undefined + private readonly otpIntervalId: NodeJS.Timeout | undefined + private readonly clearIntervalId: NodeJS.Timeout | undefined - private readonly channelsMap = new Map[]>() + private readonly channelsByWorkspace = new Map[]>() + private readonly channelById = new Map>() private readonly workspaceInfoById = new Map() private constructor ( @@ -58,12 +60,19 @@ export class PlatformWorker { private readonly repliesStorage: Collection, private readonly channelsStorage: Collection ) { - this.intervalId = setInterval( + this.otpIntervalId = setInterval( () => { void otpStorage.deleteMany({ expires: { $lte: Date.now() } }) }, 3 * 60 * 1000 ) + this.clearIntervalId = setInterval( + () => { + this.channelsByWorkspace.clear() + this.channelById.clear() + }, + 60 * 60 * 1000 + ) } public async getUsersToDisconnect (): Promise { @@ -75,8 +84,11 @@ export class PlatformWorker { } async close (): Promise { - if (this.intervalId !== undefined) { - clearInterval(this.intervalId) + if (this.otpIntervalId !== undefined) { + clearInterval(this.otpIntervalId) + } + if (this.clearIntervalId !== undefined) { + clearInterval(this.clearIntervalId) } } @@ -254,14 +266,32 @@ export class PlatformWorker { async getChannels (email: string, workspace: string): Promise[]> { const key = `${email}:${workspace}` - if (this.channelsMap.has(key)) { - return this.channelsMap.get(key) ?? [] + if (this.channelsByWorkspace.has(key)) { + return this.channelsByWorkspace.get(key) ?? [] } const res = await this.channelsStorage .find({ workspace, email }, { sort: { name: SortingOrder.Ascending } }) .toArray() - this.channelsMap.set(key, res) + this.channelsByWorkspace.set(key, res) + for (const channel of res) { + this.channelById.set(channel._id, channel) + } + return res + } + + async getChannel (email: string, channelId: ObjectId): Promise | undefined> { + if (this.channelById.has(channelId)) { + const channel = this.channelById.get(channelId) + + return channel !== undefined && channel.email === email ? channel : undefined + } + + const res = (await this.channelsStorage.findOne({ _id: channelId, email })) ?? undefined + + if (res !== undefined) { + this.channelById.set(res._id, res) + } return res } @@ -317,7 +347,12 @@ export class PlatformWorker { await this.channelsStorage.deleteMany({ _id: { $in: toDelete.map((c) => c._id) } }) } - this.channelsMap.delete(`${email}:${workspace}`) + this.channelsByWorkspace.delete(`${email}:${workspace}`) + for (const [key, channel] of this.channelById.entries()) { + if (channel.email === email) { + this.channelById.delete(key) + } + } } async getWorkspaceInfo (workspaceId: string): Promise {