Skip to content

Commit 17dfa6b

Browse files
committed
feat: Implement general bot commands and date handling
- Added `GeneralCommands` class to handle common bot commands such as help, start, commands, and bot info retrieval. - Implemented the `date` command to display both Gregorian and Persian dates. - Introduced `DateCommand` class with methods to format and convert dates to Gregorian and Persian formats. - Incorporated timezone adjustments for Tehran time zone and Persian date conversion. - Added error handling for bot commands using the `@Catch` decorator to manage failures gracefully. - Updated `BotInfo.json` and `BotMessages.json` for dynamic bot response generation. This commit includes functionality for retrieving and displaying bot help, commands, and bot information, along with Persian date formatting.
1 parent 19aaab3 commit 17dfa6b

2 files changed

Lines changed: 213 additions & 0 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Context } from 'grammy';
2+
import { Catch } from '../../../decorators/ErrorHandlingDecorator';
3+
import { BotReply } from '../../../utils/chat/BotReply';
4+
import { help, start, commands } from '../../../utils/jsons/botMessages.json';
5+
import { ChatInfo } from '../../../utils/chat/ChatInfo';
6+
import { DateCommand } from './date';
7+
import { info, user_support } from '../../../../docs/BotInfo.json';
8+
/**
9+
* Reason for lowercase command names:
10+
*
11+
* Since the names of all commands are written in lowercase for uniformity,
12+
* and the commands are automatically generated, I needed to ensure that commands
13+
* can be triggered in any case (e.g., "getBotInfo", "Getbotinfo", or "getbotinfo").
14+
* To achieve this, I converted all command names to lowercase.
15+
* This way, the system can recognize commands regardless of the case
16+
* the user enters, providing a consistent user experience.
17+
*
18+
* Code for handling case-insensitivity bot/bot.ts
19+
*/
20+
export class GeneralCommands {
21+
static getMessage(ctx: Context): { commands: string; help: string; start: string } {
22+
const chatInfo = new ChatInfo(ctx);
23+
const chattype = chatInfo.getChatType();
24+
if (chattype === 'private') {
25+
return {
26+
help: help.general,
27+
commands: commands.private,
28+
start: start.Private,
29+
};
30+
}
31+
return {
32+
commands: commands.public,
33+
help: help.general,
34+
start: start.gorup,
35+
};
36+
}
37+
// === General Command Handlers ===
38+
@Catch({
39+
message: 'Error displaying help message. Please try again later.',
40+
category: 'Bot',
41+
statusCode: 500,
42+
})
43+
public static async help(ctx: Context) {
44+
const reply = new BotReply(ctx);
45+
const { help } = GeneralCommands.getMessage(ctx);
46+
const sanitizedHelp = help.replace(/([_*[\]()~`>#+\-=|{}.!])/g, '\\$1');
47+
await reply.markdownReply(sanitizedHelp);
48+
}
49+
50+
@Catch({
51+
message: 'Error starting the bot. Please try again later.',
52+
category: 'Bot',
53+
statusCode: 500,
54+
})
55+
public static async start(ctx: Context) {
56+
const reply = new BotReply(ctx);
57+
const { start } = GeneralCommands.getMessage(ctx);
58+
await reply.textReply(start);
59+
}
60+
61+
@Catch({
62+
message: 'Error retrieving the date. Please try again later.',
63+
category: 'Bot',
64+
statusCode: 500,
65+
})
66+
public static async date(ctx: Context) {
67+
const reply = new BotReply(ctx);
68+
const { gregorianDate, persianDate } = await DateCommand.date();
69+
const message = `
70+
🌍 **Gregorian Date**:
71+
${gregorianDate}
72+
73+
🌍 **Persian Date**:
74+
${persianDate}
75+
`;
76+
reply.markdownReply(message);
77+
}
78+
79+
static async commands(ctx: Context) {
80+
const reply = new BotReply(ctx);
81+
const { commands } = GeneralCommands.getMessage(ctx);
82+
await reply.textReply(commands);
83+
}
84+
@Catch({
85+
message: 'Error retrieving support contact information. Please try again later.',
86+
category: 'Bot',
87+
statusCode: 500,
88+
})
89+
/** viewSupportContact */
90+
public static async viewsupportcontact(ctx: Context) {
91+
const reply = new BotReply(ctx);
92+
93+
// Contact information from BotInfo.json
94+
const contactMessage = `
95+
**Help Contact**: ${user_support.help_contact}\n\r
96+
**Support Email**: ${user_support.support_email}
97+
**Support Groups**:
98+
${user_support.support_groups.map((group) => `- ${group.name}: ${group.link}`).join('\n')}
99+
100+
Please reach out to us for assistance.
101+
`;
102+
await reply.markdownReply(contactMessage);
103+
}
104+
@Catch({
105+
message: 'Error retrieving bot information. Please try again later.',
106+
category: 'Bot',
107+
statusCode: 500,
108+
})
109+
/** getBotInfo */
110+
public static async getbotinfo(ctx: Context) {
111+
const reply = new BotReply(ctx);
112+
113+
// Extract detailed information from BotInfo.json
114+
const botInfoMessage = `
115+
**Bot Information**:
116+
- **Name**: ${info.bot_name}
117+
- **Version**: ${info.bot_version}
118+
- **Status**: ${info.bot_status}
119+
- **Creation Date**: ${new Date(info.created_at).toLocaleString()}
120+
- **Last Updated**: ${new Date(info.last_update).toLocaleString()}
121+
- **Supported Languages**: ${info.supported_languages.join(', ')}
122+
123+
**Description**: ${info.description}
124+
125+
This bot is designed to deliver fast and secure responses to users.
126+
`;
127+
await reply.markdownReply(botInfoMessage);
128+
}
129+
}

src/bot/commands/genearl/date.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Context } from 'grammy';
2+
import { Catch } from '../../../decorators/ErrorHandlingDecorator';
3+
export function tehranZone() {
4+
// Get current date and adjust to Tehran time zone
5+
const now = new Date();
6+
const utcTime = now.getTime() + now.getTimezoneOffset() * 60000;
7+
8+
// Tehran timezone offset in minutes (UTC+3:30)
9+
const tehranOffset = 3.5 * 60;
10+
11+
// Adjust time to Tehran timezone without considering DST
12+
return new Date(utcTime + tehranOffset * 60000);
13+
}
14+
export class DateCommand {
15+
@Catch()
16+
static async date(): Promise<{
17+
gregorianDate: string;
18+
persianDate: string;
19+
}> {
20+
const tehranTime = tehranZone();
21+
// Format Gregorian Date
22+
const gregorianDate = this.formatGregorianDate(tehranTime);
23+
// Convert to Persian Date
24+
const persianDate = this.convertToPersianDate(tehranTime);
25+
return {
26+
gregorianDate,
27+
persianDate,
28+
};
29+
}
30+
static formatGregorianDate(date: Date): string {
31+
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
32+
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
33+
34+
const dayName = days[date.getDay()];
35+
const day = date.getDate();
36+
const monthName = months[date.getMonth()];
37+
const year = date.getFullYear();
38+
const hours = date.getHours().toString().padStart(2, '0');
39+
const minutes = date.getMinutes().toString().padStart(2, '0');
40+
41+
return `${dayName}, ${monthName} ${day}, ${year}, time: ${hours}:${minutes}`;
42+
}
43+
44+
static convertToPersianDate(date: Date): string {
45+
// Gy: Gregorian year
46+
// Gm: Gregorian month (1-12)
47+
// Gd: Gregorian day of the month (1-31)
48+
const persianDate = this.toJalaali(date.getFullYear(), date.getMonth() + 1, date.getDate());
49+
const persianDays = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه'];
50+
51+
const persianMonths = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'];
52+
const dayName = Number(date.getDay() + 1) >= 7 ? persianDays[date.getDay() - 6] : persianDays[date.getDay() + 1];
53+
const hours = date.getHours().toString().padStart(2, '0');
54+
const minutes = date.getMinutes().toString().padStart(2, '0');
55+
56+
return `${dayName} ${persianDate.jd} ${persianMonths[persianDate.jm - 1]} ${persianDate.jy} ساعت: ${hours}:${minutes}`;
57+
}
58+
59+
static toJalaali(gy: number, gm: number, gd: number) {
60+
const g_d_m = [0, 31, (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0 ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
61+
let gy2 = gm > 2 ? gy + 1 : gy;
62+
let days = 355666 + 365 * gy + Math.floor((gy2 + 3) / 4) - Math.floor((gy2 + 99) / 100) + Math.floor((gy2 + 399) / 400) + gd;
63+
64+
for (let i = 0; i < gm; ++i) days += g_d_m[i];
65+
66+
let jy = -1595 + 33 * Math.floor(days / 12053);
67+
days %= 12053;
68+
69+
jy += 4 * Math.floor(days / 1461);
70+
days %= 1461;
71+
72+
if (days > 365) {
73+
jy += Math.floor((days - 1) / 365);
74+
days = (days - 1) % 365;
75+
}
76+
77+
// Month Calculation
78+
const jm = days < 186 ? 1 + Math.floor(days / 31) : 7 + Math.floor((days - 186) / 30);
79+
80+
// Day Calculation
81+
const jd = days < 186 ? days % 31 : (days - 186) % 30;
82+
return { jy, jm, jd };
83+
}
84+
}

0 commit comments

Comments
 (0)