diff --git a/QuickRepliesApp.ts b/QuickRepliesApp.ts index 48a0d53..24578e7 100644 --- a/QuickRepliesApp.ts +++ b/QuickRepliesApp.ts @@ -1,5 +1,6 @@ import { IAppAccessors, + IAppInstallationContext, IConfigurationExtend, IEnvironmentRead, IHttp, @@ -32,10 +33,13 @@ import { import { ActionButton } from './src/enum/modals/common/ActionButtons'; import { ExecuteActionButtonHandler } from './src/handlers/ExecuteActionButtonHandler'; import { settings } from './src/config/settings'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { UserInitStorage } from './src/handlers/UserDefaultReplies'; export class QuickRepliesApp extends App { private elementBuilder: ElementBuilder; private blockBuilder: BlockBuilder; + constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } @@ -91,6 +95,36 @@ export class QuickRepliesApp extends App { blockBuilder: this.blockBuilder, }; } + + public async initializeDefaultRepliesForUser( + user: IUser, + read: IRead, + persistence: IPersistence + ): Promise { + const userInitStorage = new UserInitStorage( + persistence, + read.getPersistenceReader(), + this.getLogger() + ); + + await userInitStorage.initializeDefaultRepliesForUser(user); + } + + public async onInstall( + context: IAppInstallationContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + ): Promise { + try { + // Initialize for the admin/installer user + await this.initializeDefaultRepliesForUser(context.user, read, persistence); + this.getLogger().info('Successfully initialized default replies for admin during installation'); + } catch (error) { + this.getLogger().error(`Error in onInstall: ${error}`); + } + } + public async executeViewSubmitHandler( context: UIKitViewSubmitInteractionContext, read: IRead, @@ -98,6 +132,7 @@ export class QuickRepliesApp extends App { persistence: IPersistence, modify: IModify, ) { + const handler = new ExecuteViewSubmitHandler( this, read, @@ -109,6 +144,7 @@ export class QuickRepliesApp extends App { return await handler.handleActions(); } + public async executeViewClosedHandler( context: UIKitViewCloseInteractionContext, read: IRead, @@ -135,6 +171,9 @@ export class QuickRepliesApp extends App { persistence: IPersistence, modify: IModify, ): Promise { + // Check and initialize default replies for the user + await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence); + const handler = new ExecuteBlockActionHandler( this, read, @@ -154,6 +193,9 @@ export class QuickRepliesApp extends App { persistence: IPersistence, modify: IModify, ): Promise { + // Check and initialize default replies for the user + await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence); + const handler = new ExecuteActionButtonHandler( this, read, diff --git a/app.json b/app.json index ac25ae3..5e1fa09 100644 --- a/app.json +++ b/app.json @@ -1,54 +1,54 @@ { - "id": "e664d2cb-7beb-413a-837a-80fd840c387b", - "version": "0.0.1", - "requiredApiVersion": "^1.44.0", - "iconFile": "icon.png", - "author": { - "name": "Vipin Chaudhary", - "homepage": "https://github.com/RocketChat/Apps.QuickReplies", - "support": "https://github.com/RocketChat/Apps.QuickReplies/issues" - }, - "name": "QuickReplies", - "nameSlug": "quickreplies", - "classFile": "QuickRepliesApp.ts", - "description": "Instantly craft and send customizable responses within Rocket.Chat.", - "implements": [], - "permissions": [ - { - "name": "ui.registerButtons" - }, - { - "name": "api" - }, - { - "name": "slashcommand" - }, - { - "name": "server-setting.read" - }, - { - "name": "room.read" - }, - { - "name": "persistence" - }, - { - "name": "ui.interact" - }, - { - "name": "networking" - }, - { - "name": "message.write" - }, - { - "name": "user.read" - }, - { - "name": "room.write" - }, - { - "name": "message.read" - } - ] + "id": "e664d2cb-7beb-413a-837a-80fd840c387b", + "version": "0.0.1", + "requiredApiVersion": "^1.44.0", + "iconFile": "icon.png", + "author": { + "name": "Vipin Chaudhary", + "homepage": "https://github.com/RocketChat/Apps.QuickReplies", + "support": "https://github.com/RocketChat/Apps.QuickReplies/issues" + }, + "name": "QuickReplies", + "nameSlug": "quickreplies", + "classFile": "QuickRepliesApp.ts", + "description": "Instantly craft and send customizable responses within Rocket.Chat.", + "implements": [], + "permissions": [ + { + "name": "ui.registerButtons" + }, + { + "name": "api" + }, + { + "name": "slashcommand" + }, + { + "name": "server-setting.read" + }, + { + "name": "room.read" + }, + { + "name": "persistence" + }, + { + "name": "ui.interact" + }, + { + "name": "networking" + }, + { + "name": "message.write" + }, + { + "name": "user.read" + }, + { + "name": "room.write" + }, + { + "name": "message.read" + } + ] } \ No newline at end of file diff --git a/src/data/DefaultReplies.ts b/src/data/DefaultReplies.ts new file mode 100644 index 0000000..ed978f6 --- /dev/null +++ b/src/data/DefaultReplies.ts @@ -0,0 +1,34 @@ +import { IReply } from '../definition/reply/IReply'; + +/** + * Collection of pre-built default quick replies that will be added for new users + */ +export const getDefaultReplies = (userId: string): IReply[] => { + return [ + { + name: 'Greeting', + body: 'Hello! How may I assist you today?', + id: `${userId}-${(Date.now() - 10).toString(36)}`, + }, + { + name: 'Acknowledgment', + body: 'Thank you for reaching out. I will get back to you shortly.', + id: `${userId}-${(Date.now() - 5).toString(36)}`, + }, + { + name: 'Follow-up', + body: 'I wanted to follow up on our previous discussion. Please let me know how you\'d like to proceed.', + id: `${userId}-${Date.now().toString(36)}`, + }, + { + name: 'Apology', + body: 'I sincerely apologize for any inconvenience. We are looking into this and will resolve it as soon as possible.', + id: `${userId}-${(Date.now() + 5).toString(36)}`, + }, + { + name: 'Closing', + body: 'It was a pleasure assisting you. Please feel free to reach out for any further queries.', + id: `${userId}-${(Date.now() + 10).toString(36)}`, + }, + ]; +}; diff --git a/src/handlers/UserDefaultReplies.ts b/src/handlers/UserDefaultReplies.ts new file mode 100644 index 0000000..2f6c23e --- /dev/null +++ b/src/handlers/UserDefaultReplies.ts @@ -0,0 +1,95 @@ +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from '@rocket.chat/apps-engine/definition/metadata'; +import { + IPersistence, + IPersistenceRead, + ILogger, +} from '@rocket.chat/apps-engine/definition/accessors'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { ReplyStorage } from '../storage/ReplyStorage'; +import { getDefaultReplies } from '../data/DefaultReplies'; +import { Language } from '../lib/Translation/translation'; + +export class UserInitStorage { + constructor( + private readonly persistence: IPersistence, + private readonly persistenceRead: IPersistenceRead, + private readonly logger: ILogger, + ) {} + + /** + * Check if a user has been initialized with default replies + */ + public async isUserInitialized(user: IUser): Promise { + try { + const association = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${user.id}#initialized_replies`, + ); + const result = await this.persistenceRead.readByAssociation(association); + + return result && result.length > 0; + } catch (error) { + this.logger.error(`Error checking initialization status: ${error}`); + return false; + } + } + + /** + * Mark a user as initialized in persistent storage + */ + public async markUserAsInitialized(user: IUser): Promise { + try { + const association = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + `${user.id}#initialized_replies`, + ); + await this.persistence.updateByAssociation( + association, + { initialized: true, timestamp: new Date().toISOString() }, + true + ); + this.logger.debug(`User ${user.id} marked as initialized in persistence`); + } catch (error) { + this.logger.error(`Error marking user as initialized: ${error}`); + } + } + + /** + * Initialize default quick replies for a user who hasn't used the app before + */ + public async initializeDefaultRepliesForUser(user: IUser): Promise { + try { + // Check if the user has already been initialized + if (await this.isUserInitialized(user)) { + this.logger.debug(`User ${user.id} already initialized, skipping`); + return; + } + + const replyStorage = new ReplyStorage(this.persistence, this.persistenceRead); + const existingReplies = await replyStorage.getReplyForUser(user); + + // Only initialize if the user doesn't have any replies yet + if (existingReplies.length === 0) { + const defaultReplies = getDefaultReplies(user.id); + + for (const reply of defaultReplies) { + await replyStorage.createReply( + user, + reply.name, + reply.body, + Language.en + ); + } + + this.logger.info(`Initialized default quick replies for user: ${user.id}`); + } + + await this.markUserAsInitialized(user); + } catch (error) { + this.logger.error(`Error initializing default replies for user: ${error}`); + } + } +}