diff --git a/DialogflowApp.ts b/DialogflowApp.ts index a17b4c3..6e66d19 100644 --- a/DialogflowApp.ts +++ b/DialogflowApp.ts @@ -14,15 +14,16 @@ import { ILivechatMessage } from '@rocket.chat/apps-engine/definition/livechat'; import { IPostMessageSent } from '@rocket.chat/apps-engine/definition/messages'; import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; import { ISetting } from '@rocket.chat/apps-engine/definition/settings'; -import { IUIKitLivechatInteractionHandler, IUIKitResponse, UIKitLivechatBlockInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { IUIKitInteractionHandler, IUIKitLivechatInteractionHandler, IUIKitResponse, UIKitBlockInteractionContext, UIKitLivechatBlockInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; import { settings } from './config/Settings'; import { FulfillmentsEndpoint } from './endpoints/FulfillmentsEndpoint'; import { IncomingEndpoint } from './endpoints/IncomingEndpoint'; +import { ExecuteBlockActionHandler } from './handler/ExecuteBlockActionHandler'; import { ExecuteLivechatBlockActionHandler } from './handler/ExecuteLivechatBlockActionHandler'; import { OnSettingUpdatedHandler } from './handler/OnSettingUpdatedHandler'; import { PostMessageSentHandler } from './handler/PostMessageSentHandler'; -export class DialogflowApp extends App implements IPostMessageSent, IUIKitLivechatInteractionHandler { +export class DialogflowApp extends App implements IPostMessageSent, IUIKitLivechatInteractionHandler, IUIKitInteractionHandler { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } @@ -36,6 +37,15 @@ export class DialogflowApp extends App implements IPostMessageSent, IUIKitLivech return await handler.run(); } + public async executeBlockActionHandler(context: UIKitBlockInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify): Promise { + const handler = new ExecuteBlockActionHandler(this, context, read, http, persistence, modify); + return await handler.run(); + } + public async executePostMessageSent(message: ILivechatMessage, read: IRead, http: IHttp, diff --git a/app.json b/app.json index cdedeec..3359117 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "id": "21b7d3ba-031b-41d9-8ff2-fbbfa081ae90", - "version": "1.2.3", + "version": "1.2.4", "requiredApiVersion": "^1.17.0", "iconFile": "icon.png", "author": { @@ -14,6 +14,7 @@ "description": "Integration between Rocket.Chat and the Dialogflow Chatbot platform", "implements": [ "IPostMessageSent", - "IUIKitLivechatInteractionHandler" + "IUIKitLivechatInteractionHandler", + "IUIKitInteractionHandler" ] } \ No newline at end of file diff --git a/config/Settings.ts b/config/Settings.ts index 864e257..9bc0c0b 100644 --- a/config/Settings.ts +++ b/config/Settings.ts @@ -12,6 +12,7 @@ export enum AppSetting { DialogflowHandoverFailedMessage = 'dialogflow_no_agents_online_for_handover', DialogflowCloseChatMessage = 'dialogflow_close_chat_message', DialogflowHideQuickReplies = 'dialogflow_hide_quick_replies', + DialogflowAllowDirectMessage = 'dialogflow_allow_direct_message', } export enum DefaultMessage { @@ -119,4 +120,14 @@ export const settings: Array = [ i18nDescription: 'dialogflow_hide_quick_replies_description', required: true, }, + { + id: AppSetting.DialogflowAllowDirectMessage, + public: true, + type: SettingType.BOOLEAN, + packageValue: true, + value: true, + i18nLabel: 'dialogflow_allow_direct_message', + i18nDescription: 'dialogflow_allow_direct_message_description', + required: true, + }, ]; diff --git a/handler/ExecuteBlockActionHandler.ts b/handler/ExecuteBlockActionHandler.ts new file mode 100644 index 0000000..a928022 --- /dev/null +++ b/handler/ExecuteBlockActionHandler.ts @@ -0,0 +1,82 @@ +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { IUIKitResponse, UIKitBlockInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { AppSetting } from '../config/Settings'; +import { ActionIds } from '../enum/ActionIds'; +import { createDirectMessage, deleteAllActionBlocks } from '../lib/Message'; +import { getAppSettingValue } from '../lib/Settings'; + +export class ExecuteBlockActionHandler { + constructor(private readonly app: IApp, + private context: UIKitBlockInteractionContext, + private read: IRead, + private http: IHttp, + private persistence: IPersistence, + private modify: IModify) {} + + public async run(): Promise { + try { + const interactionData = this.context.getInteractionData(); + const { room, container: { id, type }, value, actionId, message } = interactionData; + + if (type !== UIKitIncomingInteractionContainerType.MESSAGE || !room || !message) { + return this.context.getInteractionResponder().successResponse(); + } + + const { type: roomType, userIds, id: rid } = room; + + if (!roomType || roomType !== RoomType.DIRECT_MESSAGE) { + return this.context.getInteractionResponder().successResponse(); + } + + const directMessageAllowed = await getAppSettingValue(this.read, AppSetting.DialogflowAllowDirectMessage); + if (!directMessageAllowed) { + return this.context.getInteractionResponder().successResponse(); + } + + // check if the DM is of the bot + if (!userIds) { + return this.context.getInteractionResponder().successResponse(); + } + if (!userIds.some((userId) => message.sender.id === userId)) { + return this.context.getInteractionResponder().successResponse(); + } + + const appUser = await this.read.getUserReader().getAppUser(this.app.getID()) as IUser; + switch (actionId) { + case ActionIds.PERFORM_HANDOVER: + break; + + case ActionIds.CLOSE_CHAT: + break; + default: { + const otherUserId = (userIds as any).find((userId) => userId !== message.sender.id); + if (!otherUserId) { + return this.context.getInteractionResponder().errorResponse(); + } + + const otherUser = await this.read.getUserReader().getById(otherUserId); + if (!otherUser) { + return this.context.getInteractionResponder().errorResponse(); + } + + await createDirectMessage(this.app, rid, this.read, this.modify, { text: value }, otherUser); + break; + } + } + + const { value: hideQuickRepliesSetting } = await this.read.getEnvironmentReader().getSettings().getById(AppSetting.DialogflowHideQuickReplies); + if (hideQuickRepliesSetting) { + await deleteAllActionBlocks(this.modify, appUser, id); + } + + return this.context.getInteractionResponder().successResponse(); + } catch (error) { + this.app.getLogger().error(error); + return this.context.getInteractionResponder().errorResponse(); + } + } +} diff --git a/handler/PostMessageSentHandler.ts b/handler/PostMessageSentHandler.ts index 9dc38da..639d096 100644 --- a/handler/PostMessageSentHandler.ts +++ b/handler/PostMessageSentHandler.ts @@ -22,19 +22,45 @@ export class PostMessageSentHandler { const { text, editedAt, room, token, sender } = this.message; const livechatRoom = room as ILivechatRoom; - const { id: rid, type, servedBy, isOpen } = livechatRoom; + const { id: rid, type, servedBy, isOpen, userIds } = livechatRoom; const DialogflowBotUsername: string = await getAppSettingValue(this.read, AppSetting.DialogflowBotUsername); - if (!type || type !== RoomType.LIVE_CHAT) { + if (!type) { return; } - if (!isOpen || !token || editedAt || !text) { - return; + switch (type) { + case RoomType.LIVE_CHAT: { + if (!isOpen || !token) { + return; + } + if (!servedBy || servedBy.username !== DialogflowBotUsername) { + return; + } + break; + } + case RoomType.DIRECT_MESSAGE: { + const directMessageAllowed = await getAppSettingValue(this.read, AppSetting.DialogflowAllowDirectMessage); + if (!directMessageAllowed) { + return; + } + + // check if the DM is of the bot + if (!userIds) { + return; + } + if (!userIds.some((userId) => sender.id === userId)) { + return; + } + break; + } + default: { + return; + } } - if (!servedBy || servedBy.username !== DialogflowBotUsername) { + if (editedAt || !text) { return; } diff --git a/i18n/en.json b/i18n/en.json index f4a6491..6061289 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -16,5 +16,7 @@ "dialogflow_hide_quick_replies": "Hide Quick Replies", "dialogflow_hide_quick_replies_description": "If enabled, then all quick-replies will hide when a visitor clicks on any one of them", "dialogflow_handover_failed_message": "Handover Failed Message", - "dialogflow_handover_failed_message_description": "The Bot will send this message to Visitor if the handover failed because no agents were online" + "dialogflow_handover_failed_message_description": "The Bot will send this message to Visitor if the handover failed because no agents were online", + "dialogflow_allow_direct_message": "Enable support for Direct Messages", + "dialogflow_allow_direct_message_description": "If enabled, then the Dialogflow bot will also answer any direct messages to the Bot (defined in 'Bot Username' setting) " } diff --git a/lib/Message.ts b/lib/Message.ts index d7103ed..89c0193 100644 --- a/lib/Message.ts +++ b/lib/Message.ts @@ -150,3 +150,37 @@ export const deleteAllActionBlocks = async (modify: IModify, appUser: IUser, msg msgBuilder.setEditor(appUser).setBlocks(withoutActionBlocks); return modify.getUpdater().finish(msgBuilder); }; + +export const createDirectMessage = async (app: IApp, rid: string, read: IRead, modify: IModify, message: any, sender: IUser): Promise => { + if (!message) { + return; + } + + const room = await read.getRoomReader().getById(rid); + if (!room) { + app.getLogger().error(`${Logs.INVALID_ROOM_ID} ${rid}`); + return; + } + + const msg = modify.getCreator().startMessage().setRoom(room).setSender(sender); + + const { text, blocks, attachment } = message; + + if (text) { + msg.setText(text); + } + + if (attachment) { + msg.addAttachment(attachment); + } + + if (blocks) { + msg.addBlocks(blocks); + } + + return new Promise(async (resolve) => { + modify.getCreator().finish(msg) + .then((result) => resolve(result)) + .catch((error) => console.error(error)); + }); +};