mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-7915: Support tg bot attachments (#6471)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
e7a893cd4b
commit
2752d5fa03
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
@ -435,7 +435,10 @@
|
|||||||
"MONGO_DB": "telegram-bot",
|
"MONGO_DB": "telegram-bot",
|
||||||
"SECRET": "secret",
|
"SECRET": "secret",
|
||||||
"ACCOUNTS_URL": "http://localhost:3000",
|
"ACCOUNTS_URL": "http://localhost:3000",
|
||||||
"SERVICE_ID": "telegram-bot-service"
|
"SERVICE_ID": "telegram-bot-service",
|
||||||
|
"MINIO_ACCESS_KEY": "minioadmin",
|
||||||
|
"MINIO_SECRET_KEY": "minioadmin",
|
||||||
|
"MINIO_ENDPOINT": "localhost"
|
||||||
},
|
},
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
|
@ -37,13 +37,14 @@
|
|||||||
"@types/jest": "^29.5.5"
|
"@types/jest": "^29.5.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/activity": "^0.6.0",
|
||||||
"@hcengineering/core": "^0.6.32",
|
|
||||||
"@hcengineering/ui": "^0.6.15",
|
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
|
"@hcengineering/core": "^0.6.32",
|
||||||
"@hcengineering/notification": "^0.6.23",
|
"@hcengineering/notification": "^0.6.23",
|
||||||
|
"@hcengineering/platform": "^0.6.11",
|
||||||
|
"@hcengineering/setting": "^0.6.17",
|
||||||
"@hcengineering/templates": "^0.6.11",
|
"@hcengineering/templates": "^0.6.11",
|
||||||
"@hcengineering/setting": "^0.6.17"
|
"@hcengineering/ui": "^0.6.15"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/hcengineering/platform",
|
"repository": "https://github.com/hcengineering/platform",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { ActivityMessage } from '@hcengineering/activity'
|
||||||
import { ChannelItem } from '@hcengineering/contact'
|
import { ChannelItem } from '@hcengineering/contact'
|
||||||
import { Account, AttachedDoc, Class, Doc, Ref, Timestamp } from '@hcengineering/core'
|
import { Account, AttachedDoc, Class, Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||||
import { InboxNotification, NotificationProvider, NotificationType } from '@hcengineering/notification'
|
import { InboxNotification, NotificationProvider, NotificationType } from '@hcengineering/notification'
|
||||||
@ -58,8 +59,10 @@ export interface SharedTelegramMessages extends AttachedDoc {
|
|||||||
messages: SharedTelegramMessage[]
|
messages: SharedTelegramMessage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TelegramNotificationRecord {
|
export interface TelegramNotificationRequest {
|
||||||
notificationId: Ref<InboxNotification>
|
notificationId: Ref<InboxNotification>
|
||||||
|
messageId?: Ref<ActivityMessage>
|
||||||
|
attachments: boolean
|
||||||
workspace: string
|
workspace: string
|
||||||
account: Ref<Account>
|
account: Ref<Account>
|
||||||
title: string
|
title: string
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
TxProcessor
|
TxProcessor
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
import { TriggerControl } from '@hcengineering/server-core'
|
||||||
import telegram, { TelegramMessage, TelegramNotificationRecord } from '@hcengineering/telegram'
|
import telegram, { TelegramMessage, TelegramNotificationRequest } from '@hcengineering/telegram'
|
||||||
import { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
|
import { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
|
||||||
import setting, { Integration } from '@hcengineering/setting'
|
import setting, { Integration } from '@hcengineering/setting'
|
||||||
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
|
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
|
||||||
@ -218,6 +218,19 @@ async function getTranslatedData (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasAttachments (doc: ActivityMessage | undefined, hierarchy: Hierarchy): boolean {
|
||||||
|
if (doc === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hierarchy.isDerived(doc._class, chunter.class.ChatMessage)) {
|
||||||
|
const chatMessage = doc as ChatMessage
|
||||||
|
return (chatMessage.attachments ?? 0) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const SendTelegramNotifications: NotificationProviderFunc = async (
|
const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
types: BaseNotificationType[],
|
types: BaseNotificationType[],
|
||||||
@ -244,11 +257,13 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { title, body, quote, link } = await getTranslatedData(data, doc, control, message)
|
const { title, body, quote, link } = await getTranslatedData(data, doc, control, message)
|
||||||
const record: TelegramNotificationRecord = {
|
const record: TelegramNotificationRequest = {
|
||||||
notificationId: data._id,
|
notificationId: data._id,
|
||||||
|
messageId: message?._id,
|
||||||
account: receiver._id,
|
account: receiver._id,
|
||||||
workspace: toWorkspaceString(control.workspace),
|
workspace: toWorkspaceString(control.workspace),
|
||||||
sender: data.intlParams?.senderName?.toString() ?? formatName(sender.person?.name ?? 'System'),
|
sender: data.intlParams?.senderName?.toString() ?? formatName(sender.person?.name ?? 'System'),
|
||||||
|
attachments: hasAttachments(message, control.hierarchy),
|
||||||
title,
|
title,
|
||||||
quote,
|
quote,
|
||||||
body,
|
body,
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
"@hcengineering/activity": "^0.6.0",
|
"@hcengineering/activity": "^0.6.0",
|
||||||
"@hcengineering/analytics": "^0.6.0",
|
"@hcengineering/analytics": "^0.6.0",
|
||||||
"@hcengineering/analytics-service": "^0.6.0",
|
"@hcengineering/analytics-service": "^0.6.0",
|
||||||
|
"@hcengineering/attachment": "^0.6.14",
|
||||||
"@hcengineering/chunter": "^0.6.20",
|
"@hcengineering/chunter": "^0.6.20",
|
||||||
"@hcengineering/client": "^0.6.18",
|
"@hcengineering/client": "^0.6.18",
|
||||||
"@hcengineering/client-resources": "^0.6.27",
|
"@hcengineering/client-resources": "^0.6.27",
|
||||||
@ -67,6 +68,7 @@
|
|||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"@hcengineering/server-client": "^0.6.0",
|
"@hcengineering/server-client": "^0.6.0",
|
||||||
"@hcengineering/server-core": "^0.6.1",
|
"@hcengineering/server-core": "^0.6.1",
|
||||||
|
"@hcengineering/server-storage": "^0.6.0",
|
||||||
"@hcengineering/server-token": "^0.6.11",
|
"@hcengineering/server-token": "^0.6.11",
|
||||||
"@hcengineering/setting": "^0.6.17",
|
"@hcengineering/setting": "^0.6.17",
|
||||||
"@hcengineering/telegram": "^0.6.21",
|
"@hcengineering/telegram": "^0.6.21",
|
||||||
|
@ -19,12 +19,12 @@ import telegram from '@hcengineering/telegram'
|
|||||||
import { htmlToMarkup } from '@hcengineering/text'
|
import { htmlToMarkup } from '@hcengineering/text'
|
||||||
import { message } from 'telegraf/filters'
|
import { message } from 'telegraf/filters'
|
||||||
import { toHTML } from '@telegraf/entity'
|
import { toHTML } from '@telegraf/entity'
|
||||||
import { TextMessage } from '@telegraf/entity/types/types'
|
import { Message, Update } from 'telegraf/typings/core/types/typegram'
|
||||||
|
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import { PlatformWorker } from './worker'
|
import { PlatformWorker } from './worker'
|
||||||
import { getBotCommands, getCommandsHelp } from './utils'
|
import { getBotCommands, getCommandsHelp, toTelegramFileInfo } from './utils'
|
||||||
import { NotificationRecord } from './types'
|
import { NotificationRecord, TelegramFileInfo } from './types'
|
||||||
|
|
||||||
async function onStart (ctx: Context, worker: PlatformWorker): Promise<void> {
|
async function onStart (ctx: Context, worker: PlatformWorker): Promise<void> {
|
||||||
const id = ctx.from?.id
|
const id = ctx.from?.id
|
||||||
@ -116,9 +116,15 @@ async function findNotificationRecord (
|
|||||||
return await worker.getNotificationRecordById(reply.notificationId, email)
|
return await worker.getNotificationRecordById(reply.notificationId, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReplyMessage = Update.New &
|
||||||
|
Update.NonChannel &
|
||||||
|
Message.TextMessage &
|
||||||
|
(Message.PhotoMessage | Message.VoiceMessage | Message.VideoMessage | Message.VideoNoteMessage)
|
||||||
|
|
||||||
async function onReply (
|
async function onReply (
|
||||||
|
ctx: Context,
|
||||||
from: number,
|
from: number,
|
||||||
message: TextMessage,
|
message: ReplyMessage,
|
||||||
messageId: number,
|
messageId: number,
|
||||||
replyTo: number,
|
replyTo: number,
|
||||||
worker: PlatformWorker,
|
worker: PlatformWorker,
|
||||||
@ -142,7 +148,10 @@ async function onReply (
|
|||||||
|
|
||||||
await worker.saveReply({ replyId: messageId, telegramId: from, notificationId: notification.notificationId })
|
await worker.saveReply({ replyId: messageId, telegramId: from, notificationId: notification.notificationId })
|
||||||
|
|
||||||
return await worker.reply(notification, htmlToMarkup(toHTML(message)))
|
const file = await toTelegramFileInfo(ctx, message)
|
||||||
|
const files: TelegramFileInfo[] = file !== undefined ? [file] : []
|
||||||
|
|
||||||
|
return await worker.reply(notification, htmlToMarkup(toHTML(message)), files)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
|
export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
|
||||||
@ -166,16 +175,17 @@ export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
|
|||||||
|
|
||||||
const replyTo = message.reply_to_message
|
const replyTo = message.reply_to_message
|
||||||
const isReplied = await onReply(
|
const isReplied = await onReply(
|
||||||
|
ctx,
|
||||||
id,
|
id,
|
||||||
message as TextMessage,
|
message as ReplyMessage,
|
||||||
message.message_id,
|
message.message_id,
|
||||||
replyTo.message_id,
|
replyTo.message_id,
|
||||||
worker,
|
worker,
|
||||||
ctx.from.username
|
ctx.from.username
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isReplied) {
|
if (!isReplied) {
|
||||||
await ctx.react('👍')
|
await ctx.reply('Cannot reply to this message.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ import express, { type Express, type NextFunction, type Request, type Response }
|
|||||||
import { IncomingHttpHeaders, type Server } from 'http'
|
import { IncomingHttpHeaders, type Server } from 'http'
|
||||||
import { MeasureContext } from '@hcengineering/core'
|
import { MeasureContext } from '@hcengineering/core'
|
||||||
import { Telegraf } from 'telegraf'
|
import { Telegraf } from 'telegraf'
|
||||||
import telegram, { TelegramNotificationRecord } from '@hcengineering/telegram'
|
import telegram, { TelegramNotificationRequest } from '@hcengineering/telegram'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
|
|
||||||
import { ApiError } from './error'
|
import { ApiError } from './error'
|
||||||
import { PlatformWorker } from './worker'
|
import { PlatformWorker } from './worker'
|
||||||
import { Limiter } from './limiter'
|
import { Limiter } from './limiter'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import { toTelegramHtml } from './utils'
|
import { toTelegramHtml, toMediaGroups } from './utils'
|
||||||
|
|
||||||
const extractCookieToken = (cookie?: string): Token | null => {
|
const extractCookieToken = (cookie?: string): Token | null => {
|
||||||
if (cookie === undefined || cookie === null) {
|
if (cookie === undefined || cookie === null) {
|
||||||
@ -182,7 +182,7 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
|||||||
throw new ApiError(400)
|
throw new ApiError(400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationRecords = req.body as TelegramNotificationRecord[]
|
const notificationRequests = req.body as TelegramNotificationRequest[]
|
||||||
const userRecord = await worker.getUserRecordByEmail(token.email)
|
const userRecord = await worker.getUserRecordByEmail(token.email)
|
||||||
|
|
||||||
if (userRecord === undefined) {
|
if (userRecord === undefined) {
|
||||||
@ -193,21 +193,37 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
|||||||
ctx.info('Received notification', {
|
ctx.info('Received notification', {
|
||||||
email: token.email,
|
email: token.email,
|
||||||
username: userRecord.telegramUsername,
|
username: userRecord.telegramUsername,
|
||||||
ids: notificationRecords.map((it) => it.notificationId)
|
ids: notificationRequests.map((it) => it.notificationId)
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const notificationRecord of notificationRecords) {
|
for (const request of notificationRequests) {
|
||||||
void limiter.add(userRecord.telegramId, async () => {
|
void limiter.add(userRecord.telegramId, async () => {
|
||||||
const formattedMessage = toTelegramHtml(notificationRecord)
|
const { full: fullMessage, short: shortMessage } = toTelegramHtml(request)
|
||||||
const message = await bot.telegram.sendMessage(userRecord.telegramId, formattedMessage, {
|
const files = await worker.getFiles(request)
|
||||||
parse_mode: 'HTML'
|
const messageIds: number[] = []
|
||||||
})
|
|
||||||
await worker.addNotificationRecord({
|
if (files.length === 0) {
|
||||||
notificationId: notificationRecord.notificationId,
|
const message = await bot.telegram.sendMessage(userRecord.telegramId, fullMessage, {
|
||||||
email: userRecord.email,
|
parse_mode: 'HTML'
|
||||||
workspace: notificationRecord.workspace,
|
})
|
||||||
telegramId: message.message_id
|
|
||||||
})
|
messageIds.push(message.message_id)
|
||||||
|
} else {
|
||||||
|
const groups = toMediaGroups(files, fullMessage, shortMessage)
|
||||||
|
for (const group of groups) {
|
||||||
|
const mediaGroup = await bot.telegram.sendMediaGroup(userRecord.telegramId, group)
|
||||||
|
messageIds.push(...mediaGroup.map((it) => it.message_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const messageId of messageIds) {
|
||||||
|
await worker.addNotificationRecord({
|
||||||
|
notificationId: request.notificationId,
|
||||||
|
email: userRecord.email,
|
||||||
|
workspace: request.workspace,
|
||||||
|
telegramId: messageId
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ import serverClient from '@hcengineering/server-client'
|
|||||||
import { SplitLogger, configureAnalytics } from '@hcengineering/analytics-service'
|
import { SplitLogger, configureAnalytics } from '@hcengineering/analytics-service'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import type { StorageConfiguration } from '@hcengineering/server-core'
|
||||||
|
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
|
||||||
|
|
||||||
import config from './config'
|
import config from './config'
|
||||||
import { createServer, listen } from './server'
|
import { createServer, listen } from './server'
|
||||||
@ -47,7 +49,10 @@ export const start = async (): Promise<void> => {
|
|||||||
setMetadata(serverClient.metadata.UserAgent, config.ServiceId)
|
setMetadata(serverClient.metadata.UserAgent, config.ServiceId)
|
||||||
registerLoaders()
|
registerLoaders()
|
||||||
|
|
||||||
const worker = await PlatformWorker.create()
|
const storageConfig: StorageConfiguration = storageConfigFromEnv()
|
||||||
|
const storageAdapter = buildStorageFromConfig(storageConfig, config.MongoURL)
|
||||||
|
|
||||||
|
const worker = await PlatformWorker.create(ctx, storageAdapter)
|
||||||
const bot = await setUpBot(worker)
|
const bot = await setUpBot(worker)
|
||||||
const app = createServer(bot, worker, ctx)
|
const app = createServer(bot, worker, ctx)
|
||||||
|
|
||||||
|
@ -42,3 +42,18 @@ export interface OtpRecord {
|
|||||||
expires: Timestamp
|
expires: Timestamp
|
||||||
createdOn: Timestamp
|
createdOn: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlatformFileInfo {
|
||||||
|
filename: string
|
||||||
|
type: string
|
||||||
|
buffer: Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TelegramFileInfo {
|
||||||
|
type: string
|
||||||
|
url: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
name?: string
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
|
|
||||||
import { Collection } from 'mongodb'
|
import { Collection } from 'mongodb'
|
||||||
import otpGenerator from 'otp-generator'
|
import otpGenerator from 'otp-generator'
|
||||||
import { BotCommand } from 'telegraf/typings/core/types/typegram'
|
import { BotCommand, Message } from 'telegraf/typings/core/types/typegram'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import telegram, { TelegramNotificationRecord } from '@hcengineering/telegram'
|
import telegram, { TelegramNotificationRequest } from '@hcengineering/telegram'
|
||||||
import { Parser } from 'htmlparser2'
|
import { Parser } from 'htmlparser2'
|
||||||
|
import { MediaGroup } from 'telegraf/typings/telegram-types'
|
||||||
|
import { InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo } from 'telegraf/src/core/types/typegram'
|
||||||
|
import { Context, Input } from 'telegraf'
|
||||||
|
|
||||||
import { OtpRecord } from './types'
|
import { OtpRecord, PlatformFileInfo, TelegramFileInfo } from './types'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
|
|
||||||
export async function getNewOtp (otpCollection: Collection<OtpRecord>): Promise<string> {
|
export async function getNewOtp (otpCollection: Collection<OtpRecord>): Promise<string> {
|
||||||
@ -73,17 +76,27 @@ const maxQuoteLength = 500
|
|||||||
const maxBodyLength = 2000
|
const maxBodyLength = 2000
|
||||||
const maxSenderLength = 100
|
const maxSenderLength = 100
|
||||||
|
|
||||||
export function toTelegramHtml (record: TelegramNotificationRecord): string {
|
export function toTelegramHtml (record: TelegramNotificationRequest): {
|
||||||
|
full: string
|
||||||
|
short: string
|
||||||
|
} {
|
||||||
const title =
|
const title =
|
||||||
record.title !== '' ? `<a href='${record.link}'>${platformToTelegram(record.title, maxTitleLength)}</a>` + '\n' : ''
|
record.title !== '' ? `<a href='${record.link}'>${platformToTelegram(record.title, maxTitleLength)}</a>` + '\n' : ''
|
||||||
const quote =
|
const quote =
|
||||||
record.quote !== undefined && record.quote !== ''
|
record.quote !== undefined && record.quote !== ''
|
||||||
? `<blockquote>${platformToTelegram(record.quote, maxQuoteLength)}</blockquote>` + '\n'
|
? `<blockquote>${platformToTelegram(record.quote, maxQuoteLength)}</blockquote>` + '\n'
|
||||||
: ''
|
: ''
|
||||||
const body = platformToTelegram(record.body, maxBodyLength)
|
const rawBody = platformToTelegram(record.body, maxBodyLength)
|
||||||
|
const body = rawBody === '' ? '' : rawBody + '\n'
|
||||||
const sender = `<i>— ${record.sender.slice(0, maxSenderLength)}</i>`
|
const sender = `<i>— ${record.sender.slice(0, maxSenderLength)}</i>`
|
||||||
|
|
||||||
return title + quote + body + '\n' + sender
|
const full = title + quote + body + sender
|
||||||
|
const short = title + sender
|
||||||
|
|
||||||
|
return {
|
||||||
|
full,
|
||||||
|
short
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedTags = ['strong', 'em', 's', 'blockquote', 'code', 'a']
|
const supportedTags = ['strong', 'em', 's', 'blockquote', 'code', 'a']
|
||||||
@ -99,7 +112,7 @@ export function platformToTelegram (message: string, limit: number): string {
|
|||||||
>()
|
>()
|
||||||
|
|
||||||
const parser = new Parser({
|
const parser = new Parser({
|
||||||
onopentag: (tag, attrs) => {
|
onopentag: (tag) => {
|
||||||
if (tag === 'br' || tag === 'p') {
|
if (tag === 'br' || tag === 'p') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -178,3 +191,105 @@ export function platformToTelegram (message: string, limit: number): string {
|
|||||||
|
|
||||||
return newMessage.trim()
|
return newMessage.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toTgMediaFile (
|
||||||
|
file: PlatformFileInfo,
|
||||||
|
caption: string
|
||||||
|
): InputMediaPhoto | InputMediaVideo | InputMediaAudio | InputMediaDocument {
|
||||||
|
const { type, filename, buffer } = file
|
||||||
|
|
||||||
|
if (type.startsWith('image/')) {
|
||||||
|
return {
|
||||||
|
type: 'photo',
|
||||||
|
caption,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
media: Input.fromBuffer(buffer, filename)
|
||||||
|
}
|
||||||
|
} else if (type.startsWith('video/')) {
|
||||||
|
return {
|
||||||
|
type: 'video',
|
||||||
|
caption,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
media: Input.fromBuffer(buffer, filename)
|
||||||
|
}
|
||||||
|
} else if (type.startsWith('audio/')) {
|
||||||
|
return {
|
||||||
|
type: 'audio',
|
||||||
|
caption,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
media: Input.fromBuffer(buffer, filename)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: 'document',
|
||||||
|
caption,
|
||||||
|
parse_mode: 'HTML',
|
||||||
|
media: Input.fromBuffer(buffer, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toMediaGroups (files: PlatformFileInfo[], fullMessage: string, shortMessage: string): MediaGroup[] {
|
||||||
|
const photos: (InputMediaPhoto | InputMediaVideo)[] = []
|
||||||
|
const audios: InputMediaAudio[] = []
|
||||||
|
const documents: InputMediaDocument[] = []
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const media = toTgMediaFile(file, shortMessage)
|
||||||
|
if (media.type === 'photo' || media.type === 'video') {
|
||||||
|
photos.push(media)
|
||||||
|
} else if (media.type === 'audio') {
|
||||||
|
audios.push(media)
|
||||||
|
} else {
|
||||||
|
documents.push(media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = [photos, audios, documents].filter((it) => it.length > 0)
|
||||||
|
|
||||||
|
result[0][0].caption = fullMessage
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function toTelegramFileInfo (
|
||||||
|
ctx: Context,
|
||||||
|
message: Message.PhotoMessage | Message.VideoMessage | Message.VoiceMessage | Message.VideoNoteMessage
|
||||||
|
): Promise<TelegramFileInfo | undefined> {
|
||||||
|
try {
|
||||||
|
if ('photo' in message) {
|
||||||
|
const photos = message.photo
|
||||||
|
const photo = photos[photos.length - 1]
|
||||||
|
const { file_id: fileId, height, width, file_size: fileSize } = photo
|
||||||
|
const url = (await ctx.telegram.getFileLink(fileId)).toString()
|
||||||
|
const fileName = url.toString().split('/').pop()
|
||||||
|
return { url: url.toString(), width, height, name: fileName, size: fileSize, type: 'image/jpeg' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('video' in message) {
|
||||||
|
const video = message.video
|
||||||
|
const { file_id: fileId, height, width, file_size: fileSize, mime_type: type, file_name: fileName } = video
|
||||||
|
const url = (await ctx.telegram.getFileLink(fileId)).toString()
|
||||||
|
return { url: url.toString(), width, height, name: fileName, size: fileSize, type: type ?? 'video/mp4' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('video_note' in message) {
|
||||||
|
const videoNote = message.video_note
|
||||||
|
const { file_id: fileId, file_size: fileSize } = videoNote
|
||||||
|
const url = (await ctx.telegram.getFileLink(fileId)).toString()
|
||||||
|
return { url: url.toString(), width: 0, height: 0, size: fileSize, type: 'video/mp4' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('voice' in message) {
|
||||||
|
const voice = message.voice
|
||||||
|
const { file_id: fileId, file_size: fileSize, mime_type: type } = voice
|
||||||
|
const url = (await ctx.telegram.getFileLink(fileId)).toString()
|
||||||
|
return { url: url.toString(), width: 0, height: 0, size: fileSize, type: type ?? 'audio/ogg' }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to get file info', e)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
@ -14,10 +14,12 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import type { Collection } from 'mongodb'
|
import type { Collection } from 'mongodb'
|
||||||
import { Account, Ref, SortingOrder } from '@hcengineering/core'
|
import { Account, MeasureContext, Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { InboxNotification } from '@hcengineering/notification'
|
import { InboxNotification } from '@hcengineering/notification'
|
||||||
|
import { TelegramNotificationRequest } from '@hcengineering/telegram'
|
||||||
|
import { StorageAdapter } from '@hcengineering/server-core'
|
||||||
|
|
||||||
import { UserRecord, NotificationRecord, OtpRecord, ReplyRecord } from './types'
|
import { NotificationRecord, OtpRecord, PlatformFileInfo, ReplyRecord, TelegramFileInfo, UserRecord } from './types'
|
||||||
import { getDB } from './storage'
|
import { getDB } from './storage'
|
||||||
import { WorkspaceClient } from './workspace'
|
import { WorkspaceClient } from './workspace'
|
||||||
import { getNewOtp } from './utils'
|
import { getNewOtp } from './utils'
|
||||||
@ -31,6 +33,8 @@ export class PlatformWorker {
|
|||||||
private readonly intervalId: NodeJS.Timeout | undefined
|
private readonly intervalId: NodeJS.Timeout | undefined
|
||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
|
readonly ctx: MeasureContext,
|
||||||
|
readonly storageAdapter: StorageAdapter,
|
||||||
private readonly usersStorage: Collection<UserRecord>,
|
private readonly usersStorage: Collection<UserRecord>,
|
||||||
private readonly notificationsStorage: Collection<NotificationRecord>,
|
private readonly notificationsStorage: Collection<NotificationRecord>,
|
||||||
private readonly otpStorage: Collection<OtpRecord>,
|
private readonly otpStorage: Collection<OtpRecord>,
|
||||||
@ -86,6 +90,14 @@ export class PlatformWorker {
|
|||||||
return (await this.usersStorage.findOne({ _id: insertResult.insertedId })) ?? undefined
|
return (await this.usersStorage.findOne({ _id: insertResult.insertedId })) ?? undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFiles (request: TelegramNotificationRequest): Promise<PlatformFileInfo[]> {
|
||||||
|
if (request.messageId === undefined || !request.attachments) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const wsClient = await this.getWorkspaceClient(request.workspace)
|
||||||
|
return await wsClient.getFiles(request.messageId)
|
||||||
|
}
|
||||||
|
|
||||||
async updateTelegramUsername (userRecord: UserRecord, telegramUsername?: string): Promise<void> {
|
async updateTelegramUsername (userRecord: UserRecord, telegramUsername?: string): Promise<void> {
|
||||||
await this.usersStorage.updateOne(
|
await this.usersStorage.updateOne(
|
||||||
{ telegramId: userRecord.telegramId, email: userRecord.email },
|
{ telegramId: userRecord.telegramId, email: userRecord.email },
|
||||||
@ -133,7 +145,8 @@ export class PlatformWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceClient (workspace: string): Promise<WorkspaceClient> {
|
async getWorkspaceClient (workspace: string): Promise<WorkspaceClient> {
|
||||||
const wsClient = this.workspacesClients.get(workspace) ?? (await WorkspaceClient.create(workspace))
|
const wsClient =
|
||||||
|
this.workspacesClients.get(workspace) ?? (await WorkspaceClient.create(workspace, this.ctx, this.storageAdapter))
|
||||||
|
|
||||||
if (!this.workspacesClients.has(workspace)) {
|
if (!this.workspacesClients.has(workspace)) {
|
||||||
this.workspacesClients.set(workspace, wsClient)
|
this.workspacesClients.set(workspace, wsClient)
|
||||||
@ -154,9 +167,9 @@ export class PlatformWorker {
|
|||||||
return wsClient
|
return wsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
async reply (notification: NotificationRecord, text: string): Promise<boolean> {
|
async reply (notification: NotificationRecord, text: string, files: TelegramFileInfo[]): Promise<boolean> {
|
||||||
const client = await this.getWorkspaceClient(notification.workspace)
|
const client = await this.getWorkspaceClient(notification.workspace)
|
||||||
return await client.reply(notification, text)
|
return await client.reply(notification, text, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
async authorizeUser (code: string, email: string): Promise<UserRecord | undefined> {
|
async authorizeUser (code: string, email: string): Promise<UserRecord | undefined> {
|
||||||
@ -205,9 +218,9 @@ export class PlatformWorker {
|
|||||||
return [userStorage, notificationsStorage, otpStorage, repliesStorage]
|
return [userStorage, notificationsStorage, otpStorage, repliesStorage]
|
||||||
}
|
}
|
||||||
|
|
||||||
static async create (): Promise<PlatformWorker> {
|
static async create (ctx: MeasureContext, storageAdapter: StorageAdapter): Promise<PlatformWorker> {
|
||||||
const [userStorage, notificationsStorage, otpStorage, repliesStorage] = await PlatformWorker.createStorages()
|
const [userStorage, notificationsStorage, otpStorage, repliesStorage] = await PlatformWorker.createStorages()
|
||||||
|
|
||||||
return new PlatformWorker(userStorage, notificationsStorage, otpStorage, repliesStorage)
|
return new PlatformWorker(ctx, storageAdapter, userStorage, notificationsStorage, otpStorage, repliesStorage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,34 +13,106 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Client, getWorkspaceId, systemAccountEmail, TxFactory, WorkspaceId } from '@hcengineering/core'
|
import {
|
||||||
|
Blob,
|
||||||
|
Client,
|
||||||
|
generateId,
|
||||||
|
getWorkspaceId,
|
||||||
|
MeasureContext,
|
||||||
|
Ref,
|
||||||
|
Space,
|
||||||
|
systemAccountEmail,
|
||||||
|
TxFactory
|
||||||
|
} from '@hcengineering/core'
|
||||||
import { generateToken } from '@hcengineering/server-token'
|
import { generateToken } from '@hcengineering/server-token'
|
||||||
import notification, { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
import notification, { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
||||||
import chunter, { ThreadMessage } from '@hcengineering/chunter'
|
import chunter, { ThreadMessage } from '@hcengineering/chunter'
|
||||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||||
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
||||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||||
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
|
import { StorageAdapter } from '@hcengineering/server-core'
|
||||||
|
import { isEmptyMarkup } from '@hcengineering/text'
|
||||||
|
|
||||||
import { NotificationRecord } from './types'
|
import { NotificationRecord, PlatformFileInfo, TelegramFileInfo } from './types'
|
||||||
|
|
||||||
export class WorkspaceClient {
|
export class WorkspaceClient {
|
||||||
private constructor (
|
private constructor (
|
||||||
|
private readonly ctx: MeasureContext,
|
||||||
|
private readonly storageAdapter: StorageAdapter,
|
||||||
private readonly client: Client,
|
private readonly client: Client,
|
||||||
private readonly token: string,
|
private readonly token: string,
|
||||||
private readonly workspace: WorkspaceId
|
private readonly workspace: string
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static async create (workspace: string): Promise<WorkspaceClient> {
|
static async create (
|
||||||
|
workspace: string,
|
||||||
|
ctx: MeasureContext,
|
||||||
|
storageAdapter: StorageAdapter
|
||||||
|
): Promise<WorkspaceClient> {
|
||||||
const workspaceId = getWorkspaceId(workspace)
|
const workspaceId = getWorkspaceId(workspace)
|
||||||
const token = generateToken(systemAccountEmail, workspaceId)
|
const token = generateToken(systemAccountEmail, workspaceId)
|
||||||
const client = await connectPlatform(token)
|
const client = await connectPlatform(token)
|
||||||
|
|
||||||
return new WorkspaceClient(client, token, workspaceId)
|
return new WorkspaceClient(ctx, storageAdapter, client, token, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
async replyToMessage (message: ActivityMessage, account: PersonAccount, text: string): Promise<void> {
|
async createAttachments (
|
||||||
|
factory: TxFactory,
|
||||||
|
_id: Ref<ThreadMessage>,
|
||||||
|
space: Ref<Space>,
|
||||||
|
files: TelegramFileInfo[]
|
||||||
|
): Promise<number> {
|
||||||
|
const wsId = getWorkspaceId(this.workspace)
|
||||||
|
|
||||||
|
let attachments = 0
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(file.url)
|
||||||
|
const buffer = Buffer.from(await response.arrayBuffer())
|
||||||
|
const uuid = generateId()
|
||||||
|
await this.storageAdapter.put(this.ctx, wsId, uuid, buffer, file.type, file.size)
|
||||||
|
const tx = factory.createTxCollectionCUD<ThreadMessage, Attachment>(
|
||||||
|
chunter.class.ThreadMessage,
|
||||||
|
_id,
|
||||||
|
space,
|
||||||
|
'attachments',
|
||||||
|
factory.createTxCreateDoc<Attachment>(attachment.class.Attachment, space, {
|
||||||
|
name: file.name ?? uuid,
|
||||||
|
file: uuid as Ref<Blob>,
|
||||||
|
type: file.type,
|
||||||
|
size: file.size ?? 0,
|
||||||
|
lastModified: Date.now(),
|
||||||
|
collection: 'attachments',
|
||||||
|
attachedTo: _id,
|
||||||
|
attachedToClass: chunter.class.ThreadMessage
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await this.client.tx(tx)
|
||||||
|
attachments++
|
||||||
|
} catch (e) {
|
||||||
|
this.ctx.error('Failed to create attachment', { error: e, ...file })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
async replyToMessage (
|
||||||
|
message: ActivityMessage,
|
||||||
|
account: PersonAccount,
|
||||||
|
text: string,
|
||||||
|
files: TelegramFileInfo[]
|
||||||
|
): Promise<void> {
|
||||||
const txFactory = new TxFactory(account._id)
|
const txFactory = new TxFactory(account._id)
|
||||||
const hierarchy = this.client.getHierarchy()
|
const hierarchy = this.client.getHierarchy()
|
||||||
|
const messageId = generateId<ThreadMessage>()
|
||||||
|
const attachments = await this.createAttachments(txFactory, messageId, message.space, files)
|
||||||
|
|
||||||
|
if (attachments === 0 && isEmptyMarkup(text)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
if (hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||||
const thread = message as ThreadMessage
|
const thread = message as ThreadMessage
|
||||||
const collectionTx = txFactory.createTxCollectionCUD(
|
const collectionTx = txFactory.createTxCollectionCUD(
|
||||||
@ -48,16 +120,21 @@ export class WorkspaceClient {
|
|||||||
thread.attachedTo,
|
thread.attachedTo,
|
||||||
message.space,
|
message.space,
|
||||||
'replies',
|
'replies',
|
||||||
txFactory.createTxCreateDoc(chunter.class.ThreadMessage, message.space, {
|
txFactory.createTxCreateDoc(
|
||||||
attachedTo: thread.attachedTo,
|
chunter.class.ThreadMessage,
|
||||||
attachedToClass: thread.attachedToClass,
|
message.space,
|
||||||
objectId: thread.objectId,
|
{
|
||||||
objectClass: thread.objectClass,
|
attachedTo: thread.attachedTo,
|
||||||
message: text,
|
attachedToClass: thread.attachedToClass,
|
||||||
attachments: 0,
|
objectId: thread.objectId,
|
||||||
collection: 'replies',
|
objectClass: thread.objectClass,
|
||||||
provider: contact.channelProvider.Telegram
|
message: text,
|
||||||
})
|
attachments,
|
||||||
|
collection: 'replies',
|
||||||
|
provider: contact.channelProvider.Telegram
|
||||||
|
},
|
||||||
|
messageId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await this.client.tx(collectionTx)
|
await this.client.tx(collectionTx)
|
||||||
} else {
|
} else {
|
||||||
@ -66,16 +143,21 @@ export class WorkspaceClient {
|
|||||||
message._id,
|
message._id,
|
||||||
message.space,
|
message.space,
|
||||||
'replies',
|
'replies',
|
||||||
txFactory.createTxCreateDoc(chunter.class.ThreadMessage, message.space, {
|
txFactory.createTxCreateDoc(
|
||||||
attachedTo: message._id,
|
chunter.class.ThreadMessage,
|
||||||
attachedToClass: message._class,
|
message.space,
|
||||||
objectId: message.attachedTo,
|
{
|
||||||
objectClass: message.attachedToClass,
|
attachedTo: message._id,
|
||||||
message: text,
|
attachedToClass: message._class,
|
||||||
attachments: 0,
|
objectId: message.attachedTo,
|
||||||
collection: 'replies',
|
objectClass: message.attachedToClass,
|
||||||
provider: contact.channelProvider.Telegram
|
message: text,
|
||||||
})
|
attachments,
|
||||||
|
collection: 'replies',
|
||||||
|
provider: contact.channelProvider.Telegram
|
||||||
|
},
|
||||||
|
messageId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await this.client.tx(collectionTx)
|
await this.client.tx(collectionTx)
|
||||||
}
|
}
|
||||||
@ -84,19 +166,25 @@ export class WorkspaceClient {
|
|||||||
async replyToActivityNotification (
|
async replyToActivityNotification (
|
||||||
it: ActivityInboxNotification,
|
it: ActivityInboxNotification,
|
||||||
account: PersonAccount,
|
account: PersonAccount,
|
||||||
text: string
|
text: string,
|
||||||
|
files: TelegramFileInfo[]
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const message = await this.client.findOne(it.attachedToClass, { _id: it.attachedTo })
|
const message = await this.client.findOne(it.attachedToClass, { _id: it.attachedTo })
|
||||||
|
|
||||||
if (message !== undefined) {
|
if (message !== undefined) {
|
||||||
await this.replyToMessage(message, account, text)
|
await this.replyToMessage(message, account, text, files)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async replyToMention (it: MentionInboxNotification, account: PersonAccount, text: string): Promise<boolean> {
|
async replyToMention (
|
||||||
|
it: MentionInboxNotification,
|
||||||
|
account: PersonAccount,
|
||||||
|
text: string,
|
||||||
|
files: TelegramFileInfo[]
|
||||||
|
): Promise<boolean> {
|
||||||
const hierarchy = this.client.getHierarchy()
|
const hierarchy = this.client.getHierarchy()
|
||||||
|
|
||||||
if (!hierarchy.isDerived(it.mentionedInClass, activity.class.ActivityMessage)) {
|
if (!hierarchy.isDerived(it.mentionedInClass, activity.class.ActivityMessage)) {
|
||||||
@ -106,14 +194,14 @@ export class WorkspaceClient {
|
|||||||
const message = (await this.client.findOne(it.mentionedInClass, { _id: it.mentionedIn })) as ActivityMessage
|
const message = (await this.client.findOne(it.mentionedInClass, { _id: it.mentionedIn })) as ActivityMessage
|
||||||
|
|
||||||
if (message !== undefined) {
|
if (message !== undefined) {
|
||||||
await this.replyToMessage(message, account, text)
|
await this.replyToMessage(message, account, text, files)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reply (record: NotificationRecord, text: string): Promise<boolean> {
|
public async reply (record: NotificationRecord, text: string, files: TelegramFileInfo[]): Promise<boolean> {
|
||||||
const account = await this.client.getModel().findOne(contact.class.PersonAccount, { email: record.email })
|
const account = await this.client.getModel().findOne(contact.class.PersonAccount, { email: record.email })
|
||||||
if (account === undefined) {
|
if (account === undefined) {
|
||||||
return false
|
return false
|
||||||
@ -128,9 +216,14 @@ export class WorkspaceClient {
|
|||||||
}
|
}
|
||||||
const hierarchy = this.client.getHierarchy()
|
const hierarchy = this.client.getHierarchy()
|
||||||
if (hierarchy.isDerived(inboxNotification._class, notification.class.ActivityInboxNotification)) {
|
if (hierarchy.isDerived(inboxNotification._class, notification.class.ActivityInboxNotification)) {
|
||||||
return await this.replyToActivityNotification(inboxNotification as ActivityInboxNotification, account, text)
|
return await this.replyToActivityNotification(
|
||||||
|
inboxNotification as ActivityInboxNotification,
|
||||||
|
account,
|
||||||
|
text,
|
||||||
|
files
|
||||||
|
)
|
||||||
} else if (hierarchy.isDerived(inboxNotification._class, notification.class.MentionInboxNotification)) {
|
} else if (hierarchy.isDerived(inboxNotification._class, notification.class.MentionInboxNotification)) {
|
||||||
return await this.replyToMention(inboxNotification as MentionInboxNotification, account, text)
|
return await this.replyToMention(inboxNotification as MentionInboxNotification, account, text, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -139,6 +232,23 @@ export class WorkspaceClient {
|
|||||||
async close (): Promise<void> {
|
async close (): Promise<void> {
|
||||||
await this.client.close()
|
await this.client.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFiles (_id: Ref<ActivityMessage>): Promise<PlatformFileInfo[]> {
|
||||||
|
const attachments = await this.client.findAll(attachment.class.Attachment, { attachedTo: _id })
|
||||||
|
const res: PlatformFileInfo[] = []
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const chunks = await this.storageAdapter.read(this.ctx, { name: this.workspace }, attachment.file)
|
||||||
|
const buffer = Buffer.concat(chunks)
|
||||||
|
if (buffer.length > 0) {
|
||||||
|
res.push({
|
||||||
|
buffer,
|
||||||
|
type: attachment.type,
|
||||||
|
filename: attachment.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectPlatform (token: string): Promise<Client> {
|
async function connectPlatform (token: string): Promise<Client> {
|
||||||
|
Loading…
Reference in New Issue
Block a user