diff --git a/.gitignore b/.gitignore index 1d19979..9d8401a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ out/ Network Trash Folder Temporary Items .apdisk +.rcappsconfig diff --git a/AppsRocketChatTesterApp.ts b/AppsRocketChatTesterApp.ts index aad6f7f..c32afe0 100644 --- a/AppsRocketChatTesterApp.ts +++ b/AppsRocketChatTesterApp.ts @@ -1,20 +1,40 @@ -import { IAppAccessors, IConfigurationExtend, ILogger } from '@rocket.chat/apps-engine/definition/accessors'; +import { IAppAccessors, IConfigurationExtend, IHttp, ILogger, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { ApiSecurity, ApiVisibility } from '@rocket.chat/apps-engine/definition/api'; import { App } from '@rocket.chat/apps-engine/definition/App'; import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; import { SendMessageAsAppUserEndpoint } from './endpoints/SendMessageAsAppUser'; import { SendMessageAsUserEndpoint } from './endpoints/SendMessageAsUser'; +import { SendQRBtnMsgEndpoint } from './endpoints/SendQRBtnMsg'; + import { OpenContextualBarSlashcommand } from './slashcommands/OpenContextualBarSlashcommand'; import { TestArgumentsSlashcommand } from './slashcommands/TestArgumentsSlashcommand'; import { TestSlashcommand } from './slashcommands/TestSlashcommand'; import { TestVideoConfProvider } from './videoConfProviders/TestVideoConfProvider'; import { UnconfiguredVideoConfProvider } from './videoConfProviders/UnconfiguredVideoConfProvider'; +import { UIKitLivechatBlockInteractionContext, IUIKitResponse, IUIKitLivechatInteractionHandler } from '@rocket.chat/apps-engine/definition/uikit'; +import { ExecuteLivechatBlockActionHandler } from './handler/ExecuteLivechatBlockActionHandler'; -export class RocketChatTester extends App { +export class RocketChatTester extends App implements IUIKitLivechatInteractionHandler { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } + public async executeLivechatBlockActionHandler( + context: UIKitLivechatBlockInteractionContext, + read: IRead, + _http: IHttp, + _persistence: IPersistence, + modify: IModify, + ): Promise { + const handler = new ExecuteLivechatBlockActionHandler( + this, + context, + read, + modify, + ); + return await handler.run(); + } + public async extendConfiguration(configuration: IConfigurationExtend) { configuration.api.provideApi({ visibility: ApiVisibility.PUBLIC, @@ -22,6 +42,7 @@ export class RocketChatTester extends App { endpoints: [ new SendMessageAsAppUserEndpoint(this), new SendMessageAsUserEndpoint(this), + new SendQRBtnMsgEndpoint(this), ], }); diff --git a/app.json b/app.json index ad13d04..71cb87a 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "id": "bc4dd4a1-bf9b-408e-83a4-aba7eba0bf02", - "version": "0.0.5", + "version": "0.0.6", "requiredApiVersion": "^1.33.0", "iconFile": "icon.png", "author": { @@ -12,5 +12,7 @@ "nameSlug": "appsrocketchattester", "classFile": "AppsRocketChatTesterApp.ts", "description": "An app that provides endpoints to test Apps integration to Rocket.Chat", - "implements": [] + "implements": [ + "IUIKitLivechatInteractionHandler" + ] } \ No newline at end of file diff --git a/dist/appsrocketchattester_0.0.6.zip b/dist/appsrocketchattester_0.0.6.zip new file mode 100644 index 0000000..571ba7f Binary files /dev/null and b/dist/appsrocketchattester_0.0.6.zip differ diff --git a/endpoints/SendQRBtnMsg.ts b/endpoints/SendQRBtnMsg.ts new file mode 100644 index 0000000..adf111c --- /dev/null +++ b/endpoints/SendQRBtnMsg.ts @@ -0,0 +1,50 @@ +import { HttpStatusCode, IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api'; + +export class SendQRBtnMsgEndpoint extends ApiEndpoint { + public path = 'send-quick-reply-button-message'; + + public async get(request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise { + const { userId, roomId } = request.query; + + const room = await read.getRoomReader().getById(roomId); + + if (!room) { + return { + status: HttpStatusCode.NOT_FOUND, + content: `Room "${ roomId }" could not be found`, + }; + } + + + const user = await read.getUserReader().getById(userId); + if (!user) { + return { + status: HttpStatusCode.NOT_FOUND, + content: `User with id "${userId}" could not be found`, + }; + } + + const text = 'This is a main message within Quick Reply Button Message'; + + const blocks = modify.getCreator().getBlockBuilder(); + blocks.addSectionBlock({ + text: blocks.newMarkdownTextObject(text), + }); + blocks.addActionsBlock({ + elements: Array.from({ length: 5 }, (_, i) => blocks.newButtonElement({ + actionId: `button-${i}`, + text: blocks.newPlainTextObject(`Button ${i}`), + value: `button-${i}`, + })), + }); + + const messageBuilder = modify.getCreator().startMessage() + .setRoom(room) + .setSender(user) + .setBlocks(blocks); + + const messageId = await modify.getCreator().finish(messageBuilder); + return this.success(JSON.stringify({ messageId })); + } +} diff --git a/handler/ExecuteLivechatBlockActionHandler.ts b/handler/ExecuteLivechatBlockActionHandler.ts new file mode 100644 index 0000000..ead6073 --- /dev/null +++ b/handler/ExecuteLivechatBlockActionHandler.ts @@ -0,0 +1,71 @@ +import { + IModify, + IRead, +} from '@rocket.chat/apps-engine/definition/accessors'; +import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; +import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; +import { + IUIKitResponse, + UIKitLivechatBlockInteractionContext, +} 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 { createLivechatMessage, deleteAllActionBlocks } from '../lib/Message'; + +export class ExecuteLivechatBlockActionHandler { + constructor( + private readonly app: IApp, + private context: UIKitLivechatBlockInteractionContext, + private read: IRead, + private modify: IModify, + ) {} + + public async run(): Promise { + try { + const interactionData = this.context.getInteractionData(); + const { + visitor, + room, + container: { id, type }, + value, + message, + } = interactionData; + + console.log('interactionData', message?.sender?.username); + + if (!value || !room) { + // most likely, this button has a url to open. So we don't need to do anything here. + return this.context.getInteractionResponder().successResponse(); + } + + if (type !== UIKitIncomingInteractionContainerType.MESSAGE) { + return this.context.getInteractionResponder().successResponse(); + } + + const { servedBy: { username = null } = {}, id: rid } = + room as ILivechatRoom; + + if (!username) { + return this.context.getInteractionResponder().successResponse(); + } + + const appUser = (await this.read + .getUserReader() + .getAppUser()) as IUser; + + await createLivechatMessage( + room, + this.modify, + { text: value }, + visitor, + ); + + const result = 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/lib/Message.ts b/lib/Message.ts new file mode 100644 index 0000000..c12e956 --- /dev/null +++ b/lib/Message.ts @@ -0,0 +1,66 @@ +import { IRead, IModify } from "@rocket.chat/apps-engine/definition/accessors"; +import { IVisitor } from "@rocket.chat/apps-engine/definition/livechat"; +import { IMessageAttachment } from "@rocket.chat/apps-engine/definition/messages"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { BlockElementType, BlockType, IActionsBlock, IBlock } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; + + +interface IMessageParam { + text?: string; + blocks?: IBlock[]; + attachment?: IMessageAttachment; +} + +export const createLivechatMessage = async ( + room: IRoom, + modify: IModify, + message: IMessageParam, + visitor: IVisitor, +): Promise => { + if (!message) { + return; + } + + const msg = modify + .getCreator() + .startLivechatMessage() + .setRoom(room) + .setVisitor(visitor); + + const { text, attachment } = message; + + if (text) { + msg.setText(text); + } + + if (attachment) { + msg.addAttachment(attachment); + } + + return modify.getCreator().finish(msg); +}; + + +export const deleteAllActionBlocks = async ( + modify: IModify, + appUser: IUser, + msgId: string, +): Promise => { + const msgBuilder = await modify.getUpdater().message(msgId, appUser); + + const withoutActionBlocks: Array = msgBuilder + .getBlocks() + .filter( + (block) => + !( + block.type === BlockType.ACTIONS && + (block as IActionsBlock).elements.some( + (element) => element.type === BlockElementType.BUTTON, + ) + ), + ); + + msgBuilder.setEditor(appUser).setBlocks(withoutActionBlocks); + return modify.getUpdater().finish(msgBuilder); +};