Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/cvetdv.ru/bitrix/js/im/v2/provider/service/sending/src/ |
Upload File : |
import { Type } from 'main.core'; import { EventEmitter } from 'main.core.events'; import { Utils } from 'im.v2.lib.utils'; import { Logger } from 'im.v2.lib.logger'; import { runAction, type RunActionError } from 'im.v2.lib.rest'; import { Core } from 'im.v2.application.core'; import { EventType, RestMethod, DialogScrollThreshold, ChatType } from 'im.v2.const'; import { MessageService } from 'im.v2.provider.service.message'; import type { Store } from 'ui.vue3.vuex'; import type { ImModelChat, ImModelMessage } from 'im.v2.model'; import type { PlainMessageParams, CopilotMessageParams, FileMessageParams, PreparedMessage } from './types/sending'; export type { ForwardedEntityConfig, PanelContext, PanelContextWithMultipleIds } from './types/sending'; export class SendingService { #store: Store; static instance: SendingService = null; static getInstance(): SendingService { if (!this.instance) { this.instance = new this(); } return this.instance; } constructor() { this.#store = Core.getStore(); } async sendMessage(params: PlainMessageParams): void { const { text = '' } = params; if (!Type.isStringFilled(text)) { return; } Logger.warn('SendingService: sendMessage', params); const message = this.#prepareMessage(params); void this.#processMessageSending(message); } async sendMessageWithFiles(params: FileMessageParams): Promise { const { text = '', fileIds = [] } = params; if (!Type.isStringFilled(text) && !Type.isArrayFilled(fileIds)) { return Promise.resolve(); } Logger.warn('SendingService: sendMessage with files', params); const message: PreparedMessage = this.#prepareMessageWithFiles(params); await this.#handlePagination(message.dialogId); await this.#addLoadingMessage(message); await this.#addMessageToRecent(message); await this.#clearLastMessageViews(message.dialogId); this.#sendScrollEvent({ force: true, dialogId: message.dialogId, }); return Promise.resolve(); } async forwardMessages(params: PlainMessageParams): Promise { const { forwardIds, dialogId, text } = params; if (!Type.isArrayFilled(forwardIds)) { return Promise.resolve(); } Logger.warn('SendingService: forwardMessages', params); await this.#handlePagination(dialogId); let commentMessage = null; if (Type.isStringFilled(text)) { commentMessage = this.#prepareMessage(params); await this.#addMessageToModels(commentMessage); } const sortForwardIds = [...forwardIds].sort(); const forwardUuidMap = this.#getForwardUuidMap(sortForwardIds); const forwardedMessages = this.#prepareForwardMessages(params, forwardUuidMap); await this.#addForwardsToModels(forwardedMessages); this.#sendScrollEvent({ force: true, dialogId }); return this.#sendForwardRequest({ forwardUuidMap, commentMessage, dialogId }); } async retrySendMessage(params: { tempMessageId: string, dialogId: string }): Promise { const { tempMessageId, dialogId } = params; const unsentMessage: ImModelMessage = this.#store.getters['messages/getById'](tempMessageId); if (!unsentMessage) { return Promise.resolve(); } this.#removeMessageError(tempMessageId); const message = this.#prepareMessage({ text: unsentMessage.text, dialogId, tempMessageId: unsentMessage.id, replyId: unsentMessage.replyId, }); if (Type.isStringFilled(unsentMessage.forward.id)) { const [, forwardId] = unsentMessage.forward.id.split('/'); const forwardUuidMap = { [unsentMessage.id]: forwardId }; return this.#sendForwardRequest({ forwardUuidMap, dialogId }); } return this.#sendAndProcessMessage(message); } async sendCopilotPrompt(params: CopilotMessageParams): Promise { const { text = '' } = params; if (!Type.isStringFilled(text)) { return Promise.resolve(); } Logger.warn('SendingService: sendCopilotPrompt', params); const message = this.#preparePrompt(params); return this.#processMessageSending(message); } async #addLoadingMessage(message: PreparedMessage): Promise<void> { return this.#store.dispatch('messages/addLoadingMessage', { message }); } async #processMessageSending(message: PreparedMessage): Promise { await this.#handleAddingMessageToModels(message); return this.#sendAndProcessMessage(message); } async #handleAddingMessageToModels(message) { await this.#handlePagination(message.dialogId); await this.#addMessageToModels(message); this.#sendScrollEvent({ force: true, dialogId: message.dialogId }); } async #sendAndProcessMessage(message: PreparedMessage): Promise { const sendResult = await this.#sendMessageToServer(message) .catch((errors) => { this.#updateMessageError(message.temporaryId); this.#logSendErrors(errors, 'sendAndProcessMessage'); }); Logger.warn('SendingService: sendAndProcessMessage result -', sendResult); const { id } = sendResult; if (!id) { return Promise.resolve(); } this.#updateModels({ oldId: message.temporaryId, newId: id, dialogId: message.dialogId, }); return Promise.resolve(); } #prepareMessage(params: PlainMessageParams): PreparedMessage { const { text, tempMessageId, dialogId, replyId, forwardIds } = params; const defaultFields = { authorId: Core.getUserId(), unread: false, sending: true, }; return { text, dialogId, chatId: this.#getDialog(dialogId).chatId, temporaryId: tempMessageId ?? Utils.text.getUuidV4(), replyId, forwardIds, viewedByOthers: this.#needToSetAsViewed(dialogId), ...defaultFields, }; } #prepareMessageWithFiles(params: FileMessageParams): PreparedMessage { const { fileIds } = params; if (!Type.isArrayFilled(fileIds)) { throw new Error('SendingService: sendMessageWithFile: no fileId provided'); } return { ...this.#prepareMessage(params), params: { FILE_ID: fileIds }, }; } #preparePrompt(params: CopilotMessageParams): PreparedMessage { const { copilot } = params; if (!copilot || !copilot.promptCode) { throw new Error('SendingService: preparePrompt: no code provided'); } return { ...this.#prepareMessage(params), copilot, }; } async #handlePagination(dialogId: string): Promise { if (!this.#getDialog(dialogId).hasNextPage) { return Promise.resolve(); } Logger.warn('SendingService: sendMessage: there are unread pages, move to chat end'); const messageService = new MessageService({ chatId: this.#getDialog(dialogId).chatId }); await messageService.loadContext(this.#getDialog(dialogId).lastMessageId); this.#sendScrollEvent({ dialogId }); return Promise.resolve(); } #addMessageToModels(message: PreparedMessage): Promise { this.#addMessageToRecent(message); void this.#clearLastMessageViews(message.dialogId); return this.#store.dispatch('messages/add', message); } #addMessageToRecent(message: PreparedMessage) { const hasMessageText: boolean = Type.isStringFilled(message.text); const hasMessageFile: boolean = Type.isArrayFilled(message.params?.FILE_ID); if (hasMessageText || hasMessageFile) { void this.#store.dispatch('recent/update', { id: message.dialogId, fields: { messageId: message.temporaryId }, }); } } #sendMessageToServer(message: PreparedMessage): Promise { const fields = {}; if (message.replyId) { fields.replyId = message.replyId; } if (message.forwardIds) { fields.forwardIds = message.forwardIds; } if (message.text) { fields.message = message.text; fields.templateId = message.temporaryId; } if (message.copilot) { fields.copilot = message.copilot; } const queryData = { dialogId: message.dialogId.toString(), fields, }; return runAction(RestMethod.imV2ChatMessageSend, { data: queryData }); } #updateModels(params: { oldId: string, newId: number, dialogId: string }) { const { oldId, newId, dialogId } = params; void this.#store.dispatch('messages/updateWithId', { id: oldId, fields: { id: newId }, }); void this.#store.dispatch('chats/update', { dialogId, fields: { lastId: newId, lastMessageId: newId, }, }); void this.#store.dispatch('recent/update', { id: dialogId, fields: { messageId: newId }, }); } #updateMessageError(messageId: string) { void this.#store.dispatch('messages/update', { id: messageId, fields: { error: true }, }); } #removeMessageError(messageId: string) { void this.#store.dispatch('messages/update', { id: messageId, fields: { sending: true, error: false, }, }); } #sendScrollEvent(params: {force: boolean, dialogId: string} = {}) { const { force = false, dialogId } = params; EventEmitter.emit(EventType.dialog.scrollToBottom, { chatId: this.#getDialog(dialogId).chatId, threshold: force ? DialogScrollThreshold.none : DialogScrollThreshold.halfScreenUp, }); } #getDialog(dialogId: string): ImModelChat { return this.#store.getters['chats/get'](dialogId, true); } #getDialogByChatId(chatId: number): ImModelChat { return this.#store.getters['chats/getByChatId'](chatId, true); } #needToSetAsViewed(dialogId: string): boolean { return this.#store.getters['users/bots/isNetwork'](dialogId); } #handleForwardMessageResponse(params: { response: Object, dialogId: string, commentMessage: PreparedMessage }) { const { response, dialogId, commentMessage } = params; const { id, uuidMap } = response; if (id) { this.#updateModels({ oldId: commentMessage.temporaryId, newId: id, dialogId, }); } Object.entries(uuidMap).forEach(([uuid: string, messageId: number]) => { this.#updateModels({ oldId: uuid, newId: messageId, dialogId, }); }); } #handleForwardMessageError({ commentMessage, forwardUuidMap }) { if (commentMessage) { void this.#store.dispatch('messages/update', { id: commentMessage.temporaryId, fields: { error: true }, }); } Object.keys(forwardUuidMap).forEach((uuid: string) => { void this.#store.dispatch('messages/update', { id: uuid, fields: { error: true }, }); }); } #prepareForwardMessages(params: PlainMessageParams, forwardUuidMap: {[string]: number}): PreparedMessage[] { const { forwardIds, dialogId } = params; if (forwardIds.length === 0) { return []; } const preparedMessages = []; Object.entries(forwardUuidMap).forEach(([uuid: string, messageId: number]) => { const message: ImModelMessage = this.#store.getters['messages/getById'](messageId); if (!message) { return; } preparedMessages.push({ ...this.#prepareMessage({ dialogId, text: message.text, tempMessageId: uuid, replyId: message.replyId }), forward: this.#prepareForwardParams(messageId), attach: message.attach, isDeleted: message.isDeleted, files: message.files, }); }); return preparedMessages; } #prepareForwardParams(messageId: number): { id: string, userId: number, chatType: string, chatTitle: string } { const message: ImModelMessage = this.#store.getters['messages/getById'](messageId); const chat = this.#getDialogByChatId(message.chatId); const isForward = this.#store.getters['messages/isForward'](messageId); const userId = isForward ? message.forward.userId : message.authorId; const chatType = isForward ? message.forward.chatType : chat.type; let chatTitle = isForward ? message.forward.chatTitle : chat.name; if (chatType === ChatType.channel) { chatTitle = null; } return { id: this.#buildForwardContextId(message.chatId, messageId), userId, chatType, chatTitle, }; } #prepareSendForwardRequest(params: { forwardUuidMap: { [string]: number }, commentMessage: ?PreparedMessage, dialogId: string }): { dialogId: string, forwardIds: { [string]: number }, text?: string, temporaryId?: string } { const { dialogId, forwardUuidMap, commentMessage } = params; const requestPrams = { dialogId, forwardIds: forwardUuidMap, }; if (commentMessage) { requestPrams.text = commentMessage.text; requestPrams.temporaryId = commentMessage.temporaryId; } return requestPrams; } #addForwardsToModels(forwardedMessages: PreparedMessage[]): Promise { const addPromises = []; forwardedMessages.forEach((message) => { addPromises.push(this.#addMessageToModels(message)); }); return Promise.all(addPromises); } #getForwardUuidMap(forwardIds: number[]): { [string]: number } { const uuidMap = {}; forwardIds.forEach((id) => { uuidMap[Utils.text.getUuidV4()] = id; }); return uuidMap; } #buildForwardContextId(chatId: number, messageId: number): string { const dialogId = this.#getDialogByChatId(chatId).dialogId; if (dialogId.startsWith('chat')) { return `${dialogId}/${messageId}`; } const currentUser = Core.getUserId(); return `${dialogId}:${currentUser}/${messageId}`; } #logSendErrors(errors: RunActionError[], methodName: string) { errors.forEach((error) => { console.error(`SendingService: ${methodName} error: code: ${error.code} message: ${error.message}`); }); } #clearLastMessageViews(dialogId: string): Promise<any> { return this.#store.dispatch('chats/clearLastMessageViews', { dialogId, }); } async #sendForwardRequest({ forwardUuidMap, commentMessage, dialogId }): Promise<void> { try { const requestParams = this.#prepareSendForwardRequest({ forwardUuidMap, commentMessage, dialogId }); const response = await this.#sendMessageToServer(requestParams); Logger.warn('SendingService: forwardMessage result -', response); this.#handleForwardMessageResponse({ response, dialogId, commentMessage }); } catch (errors) { this.#handleForwardMessageError({ commentMessage, forwardUuidMap }); this.#logSendErrors(errors, 'forwardMessage'); } return Promise.resolve(); } }