Add telegram bot fixes (#6281)

This commit is contained in:
Kristina 2024-08-07 17:22:43 +04:00 committed by GitHub
parent f5cd4bf169
commit 4b7ce58fe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 115 additions and 82 deletions

View File

@ -231,6 +231,22 @@ services:
resources:
limits:
memory: 300M
# telegram-bot:
# image: hardcoreeng/telegram-bot
# restart: unless-stopped
# environment:
# - PORT=4020
# - BOT_TOKEN=token
# - MONGO_URL=mongodb://mongodb:27017
# - MONGO_DB=telegram-bot
# - SECRET=secret
# - DOMAIN=domain
# - ACCOUNTS_URL=http://account:3000
# - SERVICE_ID=telegram-bot-service
# deploy:
# resources:
# limits:
# memory: 300M
volumes:
db:
files:

View File

@ -38,5 +38,6 @@ startFront(metricsContext, {
POSTHOG_HOST: process.env.POSTHOG_HOST,
DESKTOP_UPDATES_URL: process.env.DESKTOP_UPDATES_URL,
DESKTOP_UPDATES_CHANNEL: process.env.DESKTOP_UPDATES_CHANNEL,
ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL
ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL,
TELEGRAM_BOT_URL: process.env.TELEGRAM_BOT_URL
})

View File

@ -343,7 +343,6 @@ export async function getChunterNotificationContent (
return {
title,
body,
data: message,
intlParams,
intlParamsNotLocalized
}

View File

@ -75,7 +75,7 @@ import serverNotification, {
getPersonAccount,
getPersonAccountById,
NOTIFICATION_BODY_SIZE,
NOTIFICATION_TITLE_SIZE,
PUSH_NOTIFICATION_TITLE_SIZE,
ReceiverInfo,
SenderInfo
} from '@hcengineering/server-notification'
@ -373,7 +373,7 @@ async function activityInboxNotificationToText (
body = await translate(doc.body, params)
}
return { ...params, title: title.substring(0, NOTIFICATION_TITLE_SIZE), body }
return { ...params, title, body }
}
async function commonInboxNotificationToText (
@ -461,12 +461,14 @@ export async function createPushFromInbox (
_id: Ref<Doc>,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<Tx | undefined> {
const { title, body } = await getTranslatedNotificationContent(data, _class, control)
let { title, body } = await getTranslatedNotificationContent(data, _class, control)
if (title === '' || body === '') {
return
}
title = title.slice(0, PUSH_NOTIFICATION_TITLE_SIZE)
const senderPerson = sender.person
const linkProviders = control.modelDb.findAllSync(serverView.mixin.ServerLinkIdProvider, {})
const provider = linkProviders.find(({ _id }) => _id === attachedToClass)
@ -575,7 +577,7 @@ export async function pushActivityInboxNotifications (
activityMessage: ActivityMessage,
shouldUpdateTimestamp: boolean
): Promise<TxCreateDoc<InboxNotification> | undefined> {
const content = await getNotificationContent(originTx, receiver.account, sender, object, control, activityMessage)
const content = await getNotificationContent(originTx, receiver.account, sender, object, control)
const data: Partial<Data<ActivityInboxNotification>> = {
...content,
attachedTo: activityMessage._id,
@ -606,7 +608,8 @@ export async function applyNotificationProviders (
res: Tx[],
object: Doc,
receiver: ReceiverInfo,
sender: SenderInfo
sender: SenderInfo,
message?: ActivityMessage
): Promise<void> {
const resources = await control.modelDb.findAll(serverNotification.class.NotificationProviderResources, {})
for (const [provider, types] of notifyResult.entries()) {
@ -635,7 +638,7 @@ export async function applyNotificationProviders (
const fn = await getResource(resource.fn)
const txes = await fn(control, types, object, data, receiver, sender)
const txes = await fn(control, types, object, data, receiver, sender, message)
if (txes.length > 0) {
res.push(...txes)
}
@ -724,7 +727,8 @@ export async function getNotificationTxes (
res,
object,
receiver,
sender
sender,
message
)
}
} else {

View File

@ -41,7 +41,8 @@ import core, {
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
type MeasureContext
type MeasureContext,
Markup
} from '@hcengineering/core'
import { getResource, IntlString, translate } from '@hcengineering/platform'
import serverNotification, {
@ -52,7 +53,7 @@ import serverNotification, {
SenderInfo,
TextPresenter
} from '@hcengineering/server-notification'
import { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
import { DocUpdateMessage } from '@hcengineering/activity'
import { NotifyResult } from './types'
@ -347,12 +348,10 @@ async function getFallbackNotificationFullfillment (
object: Doc,
originTx: TxCUD<Doc>,
control: TriggerControl,
sender: SenderInfo,
message?: ActivityMessage
sender: SenderInfo
): Promise<NotificationContent> {
const title: IntlString = notification.string.CommonNotificationTitle
let body: IntlString = notification.string.CommonNotificationBody
let data: string | undefined
const intlParams: Record<string, string | number> = {}
const intlParamsNotLocalized: Record<string, IntlString> = {}
@ -363,15 +362,6 @@ async function getFallbackNotificationFullfillment (
intlParams.title = await textPresenterFunc(object, control)
}
if (message !== undefined) {
const dataPresenter = getTextPresenter(message._class, control.hierarchy)
if (dataPresenter !== undefined) {
const textPresenterFunc = await getResource(dataPresenter.presenter)
data = await textPresenterFunc(message, control)
}
}
const tx = TxProcessor.extractTx(originTx)
intlParams.senderName = await getSenderName(control, sender)
@ -416,7 +406,7 @@ async function getFallbackNotificationFullfillment (
}
}
return { title, body, data, intlParams, intlParamsNotLocalized }
return { title, body, intlParams, intlParamsNotLocalized }
}
function getNotificationPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): NotificationPresenter | undefined {
@ -428,17 +418,17 @@ export async function getNotificationContent (
targetUser: PersonAccount,
sender: SenderInfo,
object: Doc,
control: TriggerControl,
message?: ActivityMessage
control: TriggerControl
): Promise<NotificationContent> {
let { title, body, data, intlParams, intlParamsNotLocalized } = await getFallbackNotificationFullfillment(
let { title, body, intlParams, intlParamsNotLocalized } = await getFallbackNotificationFullfillment(
object,
originTx,
control,
sender,
message
sender
)
let data: Markup | undefined
const actualTx = TxProcessor.extractTx(originTx) as TxCUD<Doc>
const notificationPresenter = getNotificationPresenter(actualTx.objectClass, control.hierarchy)
@ -447,7 +437,7 @@ export async function getNotificationContent (
const updateParams = await getFuillfillmentParams(object, originTx, targetUser._id, control)
title = updateParams.title
body = updateParams.body
data = updateParams?.data ?? data
data = updateParams.data
intlParams = {
...intlParams,
...updateParams.intlParams

View File

@ -39,6 +39,7 @@
},
"dependencies": {
"@hcengineering/core": "^0.6.32",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/server-core": "^0.6.1",

View File

@ -25,6 +25,7 @@ import {
} from '@hcengineering/notification'
import { Metadata, Plugin, Resource, plugin } from '@hcengineering/platform'
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
import { ActivityMessage } from '@hcengineering/activity'
/**
* @public
@ -154,7 +155,8 @@ export type NotificationProviderFunc = (
object: Doc,
data: InboxNotification,
receiver: ReceiverInfo,
sender: SenderInfo
sender: SenderInfo,
message?: ActivityMessage
) => Promise<Tx[]>
export interface NotificationProviderResources extends Doc {
@ -163,7 +165,7 @@ export interface NotificationProviderResources extends Doc {
}
export const NOTIFICATION_BODY_SIZE = 50
export const NOTIFICATION_TITLE_SIZE = 50
export const PUSH_NOTIFICATION_TITLE_SIZE = 80
/**
* @public

View File

@ -40,7 +40,7 @@ import { getTranslatedNotificationContent, getTextPresenter } from '@hcengineeri
import { generateToken } from '@hcengineering/server-token'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import { markupToHTML } from '@hcengineering/text'
import activity from '@hcengineering/activity'
import activity, { ActivityMessage } from '@hcengineering/activity'
/**
* @public
@ -142,10 +142,31 @@ async function getContactChannel (
return res?.value ?? ''
}
async function activityMessageToHtml (control: TriggerControl, message: ActivityMessage): Promise<string | undefined> {
const { hierarchy } = control
if (hierarchy.isDerived(message._class, chunter.class.ChatMessage)) {
const chatMessage = message as ChatMessage
return markupToHTML(chatMessage.message)
} else {
const resource = getTextPresenter(message._class, control.hierarchy)
if (resource !== undefined) {
const fn = await getResource(resource.presenter)
const textData = await fn(message, control)
if (textData !== undefined && textData !== '') {
return markupToHTML(textData)
}
}
}
return undefined
}
async function getTranslatedData (
data: InboxNotification,
doc: Doc,
control: TriggerControl
control: TriggerControl,
message?: ActivityMessage
): Promise<{
title: string
quote: string | undefined
@ -156,23 +177,22 @@ async function getTranslatedData (
let { title, body } = await getTranslatedNotificationContent(data, data._class, control)
let quote: string | undefined
if (hierarchy.isDerived(doc._class, chunter.class.ChatMessage)) {
const chatMessage = doc as ChatMessage
title = ''
quote = markupToHTML(chatMessage.message)
} else if (hierarchy.isDerived(doc._class, activity.class.ActivityMessage)) {
const resource = getTextPresenter(doc._class, control.hierarchy)
if (resource !== undefined) {
const fn = await getResource(resource.presenter)
const textData = await fn(doc, control)
if (textData !== undefined && textData !== '') {
title = ''
quote = markupToHTML(textData)
}
if (data.data !== undefined) {
body = markupToHTML(data.data)
} else if (message !== undefined) {
const html = await activityMessageToHtml(control, message)
if (html !== undefined) {
body = html
}
}
if (hierarchy.isDerived(doc._class, activity.class.ActivityMessage)) {
const html = await activityMessageToHtml(control, doc as ActivityMessage)
if (html !== undefined) {
title = ''
quote = html
}
}
body = data.data !== undefined ? `${markupToHTML(data.data)}` : body
return {
title,
@ -187,7 +207,8 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
doc: Doc,
data: InboxNotification,
receiver: ReceiverInfo,
sender: SenderInfo
sender: SenderInfo,
message?: ActivityMessage
): Promise<Tx[]> => {
if (types.length === 0) {
return []
@ -205,7 +226,7 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
}
try {
const { title, body, quote } = await getTranslatedData(data, doc, control)
const { title, body, quote } = await getTranslatedData(data, doc, control, message)
const record: TelegramNotificationRecord = {
notificationId: data._id,
account: receiver._id,

View File

@ -13,13 +13,13 @@
// limitations under the License.
//
import { Context, Telegraf, NarrowedContext } from 'telegraf'
import { Update, Message } from 'telegraf/typings/core/types/typegram'
import { Context, Telegraf } from 'telegraf'
import { translate } from '@hcengineering/platform'
import telegram from '@hcengineering/telegram'
import { htmlToMarkup } from '@hcengineering/text'
import { message } from 'telegraf/filters'
import { toHTML } from '@telegraf/entity'
import { TextMessage } from '@telegraf/entity/types/types'
import config from './config'
import { PlatformWorker } from './worker'
@ -91,36 +91,20 @@ async function onConnect (ctx: Context, worker: PlatformWorker): Promise<void> {
await ctx.reply(`*${code}*`, { parse_mode: 'MarkdownV2' })
}
type TextMessage = Record<'text', any> & Message.TextMessage
async function onReply (
ctx: NarrowedContext<Context<Update>, Update.MessageUpdate<TextMessage>>,
worker: PlatformWorker
): Promise<void> {
const id = ctx.chat?.id
const message = ctx.message
if (id === undefined || message.reply_to_message === undefined) {
return
}
const replyTo = message.reply_to_message
async function onReply (id: number, message: TextMessage, replyTo: number, worker: PlatformWorker): Promise<boolean> {
const userRecord = await worker.getUserRecord(id)
if (userRecord === undefined) {
return
return false
}
const notification = await worker.getNotificationRecord(replyTo.message_id, userRecord.email)
const notification = await worker.getNotificationRecord(replyTo, userRecord.email)
if (notification === undefined) {
return
return false
}
const isReplied = await worker.reply(notification, htmlToMarkup(toHTML(message)))
if (isReplied) {
await ctx.react('👍')
}
return await worker.reply(notification, htmlToMarkup(toHTML(message)))
}
export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
@ -134,8 +118,20 @@ export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
bot.command('stop', (ctx) => onStop(ctx, worker))
bot.command('connect', (ctx) => onConnect(ctx, worker))
bot.on(message('text'), async (ctx) => {
await onReply(ctx, worker)
bot.on(message('reply_to_message'), async (ctx) => {
const id = ctx.chat?.id
const message = ctx.message
if (id === undefined || message.reply_to_message === undefined) {
return
}
const replyTo = message.reply_to_message
const isReplied = await onReply(id, message as TextMessage, replyTo.message_id, worker)
if (isReplied) {
await ctx.react('👍')
}
})
const description = await translate(telegram.string.BotDescription, { app: config.App })

View File

@ -16,7 +16,6 @@
export interface Config {
Port: number
BotToken: string
FrontUrl: string
MongoURL: string
MongoDB: string
ServiceId: string
@ -35,7 +34,6 @@ const config: Config = (() => {
const params: Partial<Config> = {
Port: parseNumber(process.env.PORT) ?? 4020,
BotToken: process.env.BOT_TOKEN,
FrontUrl: process.env.FRONT_URL,
MongoURL: process.env.MONGO_URL,
MongoDB: process.env.MONGO_DB,
AccountsUrl: process.env.ACCOUNTS_URL,

View File

@ -36,11 +36,16 @@ export const start = async (): Promise<void> => {
const bot = await setUpBot(worker)
const app = createServer(bot, worker)
void bot.launch({ webhook: { domain: config.Domain, port: config.BotPort } }).then(() => {
void bot.telegram.getWebhookInfo().then((info) => {
ctx.info('Webhook info', info)
if (config.Domain === '') {
void bot.launch({ dropPendingUpdates: true })
} else {
void bot.launch({ webhook: { domain: config.Domain, port: config.BotPort }, dropPendingUpdates: true }).then(() => {
void bot.telegram.getWebhookInfo().then((info) => {
ctx.info('Webhook info', info)
})
})
})
}
app.get(`/telegraf/${bot.secretPathComponent()}`, (req, res) => {
res.status(200).send()
})