Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions QuickRepliesApp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
IAppAccessors,
IAppInstallationContext,
IConfigurationExtend,
IEnvironmentRead,
IHttp,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -91,13 +95,44 @@ export class QuickRepliesApp extends App {
blockBuilder: this.blockBuilder,
};
}

public async initializeDefaultRepliesForUser(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To much login in the root of the app can we define thing outside and use here ?

user: IUser,
read: IRead,
persistence: IPersistence
): Promise<void> {
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<void> {
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,
http: IHttp,
persistence: IPersistence,
modify: IModify,
) {

const handler = new ExecuteViewSubmitHandler(
this,
read,
Expand All @@ -109,6 +144,7 @@ export class QuickRepliesApp extends App {

return await handler.handleActions();
}

public async executeViewClosedHandler(
context: UIKitViewCloseInteractionContext,
read: IRead,
Expand All @@ -135,6 +171,9 @@ export class QuickRepliesApp extends App {
persistence: IPersistence,
modify: IModify,
): Promise<IUIKitResponse> {
// Check and initialize default replies for the user
await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence);

const handler = new ExecuteBlockActionHandler(
this,
read,
Expand All @@ -154,6 +193,9 @@ export class QuickRepliesApp extends App {
persistence: IPersistence,
modify: IModify,
): Promise<IUIKitResponse> {
// Check and initialize default replies for the user
await this.initializeDefaultRepliesForUser(context.getInteractionData().user, read, persistence);

const handler = new ExecuteActionButtonHandler(
this,
read,
Expand Down
104 changes: 52 additions & 52 deletions app.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
34 changes: 34 additions & 0 deletions src/data/DefaultReplies.ts
Original file line number Diff line number Diff line change
@@ -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)}`,
},
];
};
95 changes: 95 additions & 0 deletions src/handlers/UserDefaultReplies.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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<void> {
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<void> {
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}`);
}
}
}