Skip to content

Commit

Permalink
V1-beta
Browse files Browse the repository at this point in the history
  • Loading branch information
mehotkhan committed Sep 2, 2024
1 parent 50fa959 commit 26c531c
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 110 deletions.
38 changes: 30 additions & 8 deletions src/bot/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
NoConversationFoundMessage,
RATE_LIMIT_MESSAGE,
REPLAY_TO_MESSAGE,
SELF_MESSAGE_DISABLE_MESSAGE,
USER_BLOCKED_MESSAGE,
USER_IS_BLOCKED_MESSAGE,
USER_UNBLOCKED_MESSAGE,
Expand All @@ -17,7 +18,6 @@ import { checkRateLimit } from "../utils/tools";

/**
* Handles the reply action triggered from an inline keyboard.
*
* This function manages the process when a user clicks on the "reply" button in an inline keyboard.
* It verifies the conversation context and initiates a reply by linking it to the correct conversation.
*
Expand All @@ -37,6 +37,7 @@ export const handleReplyAction = async (
const ticketId = ctx.match[1];
const currentUserId = ctx.from?.id!;

// Retrieve the conversation data
const conversationId = getConversationId(ticketId, APP_SECURE_KEY);
const conversationData = await conversationModel.get(conversationId);

Expand All @@ -57,13 +58,21 @@ export const handleReplyAction = async (
const otherUser = await userModel.get(
parentConversation.connection.from.toString()
);
// check rate limit
const currentUser = await userModel.get(currentUserId.toString());

// disable self message
if (
parentConversation.connection.from.toString() === currentUserId.toString()
) {
await ctx.reply(SELF_MESSAGE_DISABLE_MESSAGE);
return;
}
// Check rate limit
const currentUser = await userModel.get(currentUserId.toString());
if (checkRateLimit(currentUser.lastMessage)) {
await ctx.reply(RATE_LIMIT_MESSAGE);
return;
}

// Check if the other user has blocked the current user
if (otherUser?.blockList.includes(currentUserId.toString())) {
await ctx.reply(USER_IS_BLOCKED_MESSAGE);
Expand All @@ -77,13 +86,16 @@ export const handleReplyAction = async (
reply_to_message_id: parentConversation.connection.parent_message_id,
};

// Update the user's current conversation
await userModel.updateField(
currentUserId.toString(),
"currentConversation",
conversation
);

incrementStat(statsModel, "newConversation"); // Increment the reply stat
// Increment the reply stat
incrementStat(statsModel, "newConversation");

await ctx.reply(REPLAY_TO_MESSAGE, {
reply_markup: { force_reply: true },
reply_to_message_id: ctx.callbackQuery?.message?.message_id!,
Expand All @@ -97,7 +109,6 @@ export const handleReplyAction = async (

/**
* Handles the block action triggered from an inline keyboard.
*
* This function adds a user to the block list when the "block" button is clicked.
* It ensures that further communication from the blocked user is prevented until they are unblocked.
*
Expand All @@ -117,6 +128,7 @@ export const handleBlockAction = async (
const ticketId = ctx.match[1];
const currentUserId = ctx.from?.id!;

// Retrieve the conversation data
const conversationId = getConversationId(ticketId, APP_SECURE_KEY);
const conversationData = await conversationModel.get(conversationId);

Expand All @@ -134,18 +146,23 @@ export const handleBlockAction = async (
const parentConversation: Conversation = JSON.parse(rawConversation);

try {
// Block the user
await userModel.updateField(
currentUserId.toString(),
"blockList",
parentConversation.connection.from.toString(),
true
);

await incrementStat(statsModel, "blockedUsers"); // Increment the blocked user stat
// Increment the blocked user stat
await incrementStat(statsModel, "blockedUsers");

// Send confirmation to the user
await ctx.api.sendMessage(currentUserId, USER_BLOCKED_MESSAGE, {
reply_to_message_id: ctx.callbackQuery?.message?.message_id!,
});

// Update the reply markup to reflect the block
const replyKeyboard = createMessageKeyboard(ticketId, true);
await ctx.api.editMessageReplyMarkup(
ctx.chat?.id!,
Expand All @@ -163,7 +180,6 @@ export const handleBlockAction = async (

/**
* Handles the unblock action triggered from an inline keyboard.
*
* This function removes a user from the block list when the "unblock" button is clicked,
* allowing communication to resume between the two users.
*
Expand All @@ -183,6 +199,7 @@ export const handleUnblockAction = async (
const ticketId = ctx.match[1];
const currentUserId = ctx.from?.id!;

// Retrieve the conversation data
const conversationId = getConversationId(ticketId, APP_SECURE_KEY);
const conversationData = await conversationModel.get(conversationId);

Expand All @@ -207,17 +224,22 @@ export const handleUnblockAction = async (
parentConversation.connection.from.toString()
)
) {
// Unblock the user
await userModel.popItemFromField(
currentUserId.toString(),
"blockList",
parentConversation.connection.from.toString()
);

await incrementStat(statsModel, "unblockedUsers"); // Increment the unblocked user stat
// Increment the unblocked user stat
await incrementStat(statsModel, "unblockedUsers");

// Send confirmation to the user
await ctx.api.sendMessage(currentUserId, USER_UNBLOCKED_MESSAGE, {
reply_to_message_id: ctx.callbackQuery?.message?.message_id!,
});

// Update the reply markup to reflect the unblock
const replyKeyboard = createMessageKeyboard(ticketId, false);
await ctx.api.editMessageReplyMarkup(
ctx.chat?.id!,
Expand Down
27 changes: 18 additions & 9 deletions src/bot/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import {
NEW_INBOX_MESSAGE,
NoUserFoundMessage,
RATE_LIMIT_MESSAGE,
SELF_MESSAGE_DISABLE_MESSAGE,
StartConversationMessage,
USER_IS_BLOCKED_MESSAGE,
WelcomeMessage,
YOUR_MESSAGE_SEEN_MESSAGE,
} from "../utils/messages";
import { sendDecryptedMessage } from "../utils/messageSender";
import { sendDecryptedMessage } from "../utils/sender";
import {
decryptPayload,
encryptedPayload,
Expand All @@ -32,7 +33,7 @@ import { checkRateLimit, convertToPersianNumbers } from "../utils/tools";

/**
* Handles the /start command to initiate or continue a user's interaction with the bot.
* It generates a unique UUID for new users and continues the interaction for existing users.
* Generates a unique UUID for new users and continues the interaction for existing users.
*
* @param {Context} ctx - The context of the current Telegram update.
* @param {KVModel<User>} userModel - KVModel instance for managing user data.
Expand All @@ -59,9 +60,10 @@ export const handleStartCommand = async (
userUUID: currentUserUUID,
userName: ctx.from?.first_name ?? "بدون نام!",
blockList: [],
lastMessage: Date.now(),
currentConversation: {},
});
await incrementStat(statsModel, "newUser"); // Increment the reply stat
await incrementStat(statsModel, "newUser");
} else {
currentUserUUID = currentUser.userUUID;
}
Expand All @@ -84,11 +86,18 @@ export const handleStartCommand = async (
const otherUserUUID = ctx.match;
const otherUserId = await userUUIDtoId.get(otherUserUUID);
const currentUser = await userModel.get(currentUserId.toString());
// check rate limit

// disable self message
if (otherUserId?.toString() === currentUserId.toString()) {
await ctx.reply(SELF_MESSAGE_DISABLE_MESSAGE);
return;
}
// Check rate limit
if (checkRateLimit(currentUser.lastMessage)) {
await ctx.reply(RATE_LIMIT_MESSAGE);
return;
}

if (otherUserId) {
const otherUser = await userModel.get(otherUserId);

Expand Down Expand Up @@ -242,13 +251,14 @@ export const handleMessage = async (
"currentConversation",
undefined
);
// update rate limit

// Update rate limit
await userModel.updateField(
currentUserId.toString(),
"lastMessage",
Date.now()
);
await incrementStat(statsModel, "newConversation"); // Increment the reply stat
await incrementStat(statsModel, "newConversation");
} catch (error) {
await ctx.reply(HuhMessage, {
reply_markup: mainMenu,
Expand All @@ -261,11 +271,10 @@ export const handleMessage = async (
*
* @param {Context} ctx - The context of the current Telegram update.
* @param {KVModel<User>} userModel - KVModel instance for managing user data.
* @param {KVModel<string>} conversationModel - KVModel instance for managing conversation data.
* @param {KVModel<string>} conversationModel - KVModel instance for managing conversation data.
* @param {DurableObjectNamespace} inboxNamespace - Durable Object Namespace for inbox handling.
* @param {string} APP_SECURE_KEY - The application-specific secure key.
*/

export const handleInboxCommand = async (
ctx: Context,
userModel: KVModel<User>,
Expand All @@ -284,7 +293,7 @@ export const handleInboxCommand = async (

const inbox: InboxMessage[] = await response.json();
if (inbox.length > 0) {
for (const { ticketId, timestamp } of inbox) {
for (const { ticketId } of inbox) {
try {
const conversationId = getConversationId(ticketId, APP_SECURE_KEY);
const conversationData = await conversationModel.get(conversationId);
Expand Down
89 changes: 69 additions & 20 deletions src/bot/inboxDU.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,86 @@
import { DurableObject } from "cloudflare:workers";
import { DurableObject, DurableObjectState } from "cloudflare:workers";
import { InboxMessage } from "../types";

/**
* InboxDurableObject class handles the storage and retrieval of inbox messages
* for a specific user. Each instance of this class corresponds to a unique inbox
* identified by the Durable Object ID.
*/
export class InboxDurableObject implements DurableObject {
state: DurableObjectState;
private state: DurableObjectState;

/**
* Constructor initializes the Durable Object's state.
*
* @param state - The state object provided by Cloudflare's Durable Object runtime.
* @param env - The environment object, typically containing bindings to external resources.
*/
constructor(state: DurableObjectState, env: any) {
this.state = state;
}

/**
* Handles incoming HTTP requests and routes them based on the method and URL path.
* Supports adding a message, retrieving the message count, and fetching/clearing the inbox.
*
* @param request - The incoming HTTP request.
* @returns A Response object representing the result of the operation.
*/
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const { method } = request;

if (method === "POST" && url.pathname === "/add") {
const { timestamp, ticketId } = await request.json<InboxMessage>();
const inbox =
(await this.state.storage.get<InboxMessage[]>("inbox")) || [];
inbox.push({ timestamp, ticketId });
await this.state.storage.put("inbox", inbox);
return new Response("Message added to inbox", { status: 200 });
}
if (method === "GET" && url.pathname === "/counter") {
const inbox =
(await this.state.storage.get<InboxMessage[]>("inbox")) || [];
return new Response(JSON.stringify(inbox), { status: 200 });
}
if (method === "GET" && url.pathname === "/retrieve") {
const inbox =
(await this.state.storage.get<InboxMessage[]>("inbox")) || [];
await this.state.storage.delete("inbox");
return new Response(JSON.stringify(inbox), { status: 200 });
switch (method) {
case "POST":
if (url.pathname === "/add") {
return this.addMessage(request);
}
break;
case "GET":
if (url.pathname === "/counter") {
return this.getMessageCount();
} else if (url.pathname === "/retrieve") {
return this.retrieveAndClearInbox();
}
break;
}

return new Response("Not Found", { status: 404 });
}

/**
* Adds a message to the user's inbox.
*
* @param request - The incoming HTTP request containing the message data in JSON format.
* @returns A Response indicating the result of the operation.
*/
private async addMessage(request: Request): Promise<Response> {
const { timestamp, ticketId } = await request.json<InboxMessage>();
const inbox = (await this.state.storage.get<InboxMessage[]>("inbox")) || [];
inbox.push({ timestamp, ticketId });
await this.state.storage.put("inbox", inbox);
return new Response("Message added to inbox", { status: 200 });
}

/**
* Retrieves the current count of messages in the inbox.
*
* @returns A Response containing the count of messages in the inbox as JSON.
*/
private async getMessageCount(): Promise<Response> {
const inbox = (await this.state.storage.get<InboxMessage[]>("inbox")) || [];
return new Response(JSON.stringify(inbox), { status: 200 });
}

/**
* Retrieves and clears the user's inbox.
* After the inbox is retrieved, it is deleted from the storage.
*
* @returns A Response containing the inbox messages as JSON.
*/
private async retrieveAndClearInbox(): Promise<Response> {
const inbox = (await this.state.storage.get<InboxMessage[]>("inbox")) || [];
await this.state.storage.delete("inbox");
return new Response(JSON.stringify(inbox), { status: 200 });
}
}
Loading

0 comments on commit 26c531c

Please sign in to comment.