Skip to content

Commit 47ce796

Browse files
committed
feat(chat): Add utility classes for handling bot info, replies, and chat info
- Implement `BotInfo` class for checking if the bot is an admin in a chat. - Add `BotReply` class for sending various types of replies (plain text, markdown, HTML, and inline keyboard). - Introduce `ChatInfo` class to manage chat-related functionality such as determining chat type, checking if a user is in the group, and handling topic messages. - Extend `BotReply` with methods to handle replies to messages and specific formatting options (text, markdown, HTML). - Update `ChatInfo` with new methods to check if a message is related to a topic and fetch topic details.
1 parent 954d2c3 commit 47ce796

3 files changed

Lines changed: 156 additions & 0 deletions

File tree

src/utils/chat/BotInfo.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Context } from 'grammy';
2+
3+
export class BotInfo {
4+
constructor(private _ctx: Context) {
5+
this._ctx = _ctx;
6+
}
7+
async isAdmin(): Promise<boolean> {
8+
const botInfo = this._ctx.me;
9+
if (!botInfo) {
10+
return false;
11+
}
12+
13+
const chatMember = await this._ctx.getChatMember(botInfo.id);
14+
if (!chatMember) {
15+
return false;
16+
}
17+
return ['administrator', 'creator'].includes(chatMember.status);
18+
}
19+
}

src/utils/chat/BotReply.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Context, InlineKeyboard } from 'grammy';
2+
import { RepliedMessage } from '../../types/CommandTypes';
3+
4+
export class BotReply {
5+
private ctx: Context;
6+
7+
constructor(ctx: Context) {
8+
this.ctx = ctx;
9+
}
10+
/**
11+
* Sends a generic reply without any special formatting.
12+
* @param message - The message to send.
13+
*/
14+
async send(message: string): Promise<void> {
15+
await this.ctx.reply(message);
16+
}
17+
/**
18+
* Sends a plain text reply.
19+
* @param message - The plain text message to send.
20+
* @param replyToMessage - Optionally, reply to a specific message.
21+
*/
22+
async textReply(message: string): Promise<void> {
23+
await this.ctx.reply(message, {
24+
reply_parameters: {
25+
message_id: this.ctx.message?.message_id!,
26+
},
27+
});
28+
}
29+
/**
30+
* Sends a plain text reply to a specific message.
31+
* @param message - The message to send.
32+
*/
33+
async replyToMessage(message: string): Promise<void> {
34+
await this.ctx.reply(message, {
35+
parse_mode: 'HTML',
36+
reply_parameters: {
37+
message_id: this.ctx.message?.reply_to_message?.message_id!,
38+
},
39+
});
40+
}
41+
/**
42+
* Sends a reply with Markdown formatting.
43+
* @param message - Markdown message to send.
44+
* @param replyToMessage - Whether to reply to the user's message.
45+
*/
46+
async markdownReply(message: string): Promise<void> {
47+
await this.ctx.reply(message, {
48+
parse_mode: 'Markdown',
49+
reply_parameters: {
50+
message_id: this.ctx.message?.message_id!,
51+
},
52+
});
53+
}
54+
55+
/**
56+
* Sends a reply with HTML formatting.
57+
* @param message - HTML formatted message to send.
58+
* @param replyToMessage - Whether to reply to the user's message.
59+
*/
60+
async htmlReply(message: string): Promise<void> {
61+
await this.ctx.reply(message, {
62+
parse_mode: 'HTML',
63+
reply_parameters: {
64+
message_id: this.ctx.message?.message_id!,
65+
},
66+
});
67+
}
68+
69+
/**
70+
* Sends a reply with inline keyboard options.
71+
* @param message - Message to send.
72+
* @param keyboard - Inline keyboard options.
73+
*/
74+
async inlineKeyboardReply(message: string, keyboard: InlineKeyboard): Promise<void> {
75+
await this.ctx.reply(message, { reply_markup: keyboard });
76+
}
77+
78+
async getReplyMessage(): Promise<RepliedMessage | undefined> {
79+
const repliedMessage = this.ctx.message?.reply_to_message;
80+
if (repliedMessage && repliedMessage.from) {
81+
return repliedMessage as RepliedMessage;
82+
}
83+
return undefined;
84+
}
85+
}

src/utils/chat/ChatInfo.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Context } from 'grammy';
2+
import { BotReply } from './BotReply';
3+
export class ChatInfo {
4+
constructor(private _ctx: Context) {}
5+
6+
getChatType(): 'private' | 'group' | 'supergroup' | 'channel' | 'unknown' {
7+
return this._ctx.chat?.type ?? 'unknown';
8+
}
9+
message() {
10+
return this._ctx.message;
11+
}
12+
entities(): { offset: number; length: number; type: string } | null {
13+
const entity = this._ctx.message?.entities?.[0];
14+
if (entity) {
15+
return {
16+
offset: entity.offset,
17+
length: entity.length,
18+
type: entity.type,
19+
};
20+
}
21+
return null;
22+
}
23+
async userInGroup(message: string = 'The user is no longer a member of this group.'): Promise<boolean | undefined> {
24+
const reply = new BotReply(this._ctx);
25+
const replied = await reply.getReplyMessage();
26+
const userId = replied?.from?.id;
27+
if (!userId) {
28+
await this._ctx.reply('Unable to determine the user ID. Please ensure you are replying to a valid user.', {
29+
reply_to_message_id: this._ctx.message?.message_id,
30+
});
31+
return;
32+
}
33+
34+
const chatMember = await this._ctx.getChatMember(userId);
35+
36+
if (chatMember.status === 'left' || chatMember.status === 'kicked') {
37+
await reply.textReply(message);
38+
return true;
39+
}
40+
return false;
41+
}
42+
chatIsTopic(): boolean {
43+
return Boolean(this._ctx.message?.is_topic_message);
44+
}
45+
topicName(): string | undefined {
46+
const message = this._ctx.message;
47+
if (this.chatIsTopic()) {
48+
return message?.reply_to_message?.forum_topic_created?.name;
49+
}
50+
return undefined;
51+
}
52+
}

0 commit comments

Comments
 (0)