Skip to content

Commit 9a16898

Browse files
committed
feat(messages-service): add blacklist check and user restriction functionality
- Implement `checkAndHandleBlacklistedWords` to validate user messages against a group-defined blacklist. - Automatically restrict users for 1 hour if forbidden words are detected in their messages. - Generate detailed warning messages with masked forbidden words and line numbers for better clarity. - Enhance group moderation capabilities by allowing admins and approved users to bypass blacklist checks. fix(messages-service): ensure restricted users cannot send any form of content during mute period - Refine restriction settings to comprehensively block sending messages, polls, photos, and other media. chore(utils): improve warning message formatting for better user understanding - Add placeholders for masked messages and specific details about violations.
1 parent b94fcc1 commit 9a16898

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

src/service/messages/index.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { Context } from 'grammy';
2+
import { ServiceProvider } from '../database/ServiceProvider';
3+
import { BotReply } from '../../utils/chat/BotReply';
4+
import { ChatInfo } from '../../utils/chat/ChatInfo';
5+
export class MessagesService {
6+
private _reply: BotReply;
7+
private _chatInfo: ChatInfo;
8+
constructor(private _ctx: Context) {
9+
this._reply = new BotReply(_ctx);
10+
this._chatInfo = new ChatInfo(_ctx);
11+
}
12+
async isCode() {
13+
const entities = this._ctx.message!.entities;
14+
entities?.forEach((e) => {
15+
if (e.type === 'pre' && e.language) {
16+
this._reply.textReply(`Your code is garbage.\n\n- Linus Torvalds`);
17+
}
18+
});
19+
}
20+
async isNewUser() {
21+
if (this._ctx.message?.new_chat_members?.length! > 0) {
22+
const users = this._ctx.message!.new_chat_members!;
23+
const chat = this._ctx.chat;
24+
const service = ServiceProvider.getInstance();
25+
const groupService = await service.getGroupService();
26+
let group = await groupService.getByGroupId(chat!.id);
27+
if (!group) {
28+
group = await groupService.save(this._ctx);
29+
}
30+
let welcome_message = group.welcome_message;
31+
if (!welcome_message) {
32+
welcome_message = `Welcome to ${this._ctx.chat!.title}\n\nWe are so glad to have you here!. If you have any questions, don't hesitate to ask! 💬\n\nEnjoy your time here!`;
33+
}
34+
for (const user of users) {
35+
if (user.id !== this._ctx.me?.id) {
36+
await groupService.updateMembers(this._ctx.chat!.id!, this._ctx.from?.id!);
37+
await this._reply.send(welcome_message);
38+
}
39+
}
40+
}
41+
}
42+
Spam() {}
43+
async userIsLeftGroup() {
44+
if (this._ctx.message?.left_chat_member) {
45+
const user = this._ctx.message.left_chat_member!;
46+
47+
if (user.id !== this._ctx.me?.id) {
48+
// Notify the group about the member leaving
49+
const username = user.username ? `@${user.username}` : user.first_name;
50+
await this._ctx.reply(`${username} has left the chat.`);
51+
}
52+
}
53+
}
54+
55+
async checkAndHandleBlacklistedWords() {
56+
const groupId = this._ctx.chat?.id;
57+
if (!groupId) {
58+
return;
59+
}
60+
const messageText = this._ctx.message?.text;
61+
const messageId = this._ctx.message?.message_id;
62+
const userId = this._ctx.message?.from?.id;
63+
64+
if (!messageText || !messageId || !userId) {
65+
return;
66+
}
67+
const service = ServiceProvider.getInstance();
68+
const [groupService] = await Promise.all([service.getGroupService()]);
69+
let group = await groupService.getByGroupId(groupId);
70+
if (!group) {
71+
group = await groupService.save(this._ctx);
72+
}
73+
const approvedUsers = group.approved_users.map(Number);
74+
const isAdmin = await this._chatInfo.isAdmin(this._ctx, userId);
75+
// Allow approved users and admins to bypass blacklist checks
76+
if (approvedUsers.includes(+userId) || isAdmin) {
77+
return;
78+
}
79+
const blacklist = group.black_list;
80+
const isForbidden = blacklist.some((word) => {
81+
const regex = new RegExp(`(^|\\s)${word}($|\\s|[.,!?;])`, 'i'); // \b is a word boundary, 'i' is for case-insensitive matching
82+
return regex.test(messageText);
83+
});
84+
if (isForbidden) {
85+
await this._ctx.restrictChatMember(
86+
userId,
87+
{
88+
can_send_messages: false,
89+
can_send_polls: false,
90+
can_send_other_messages: false,
91+
can_add_web_page_previews: false,
92+
can_send_photos: false,
93+
can_send_audios: false,
94+
},
95+
{ until_date: Math.floor(Date.now() / 1000) + 3600 }
96+
);
97+
await this._ctx.deleteMessage();
98+
const warningMessage = await this.generateBlacklistWarningMessage(this._ctx, blacklist);
99+
await this._reply.send(warningMessage);
100+
}
101+
}
102+
103+
private async generateBlacklistWarningMessage(ctx: Context, blacklist: string[]): Promise<string> {
104+
const info_user = ctx.message?.from?.username || ctx.message?.from.first_name;
105+
const messageText = this._ctx.message?.text;
106+
// Function to mask forbidden words and return the modified message
107+
const maskForbiddenWords = (message: string, blacklist: string[]): string => {
108+
let maskedMessage = message;
109+
blacklist.forEach((word) => {
110+
const regex = new RegExp(`(^|\\s)${word}($|\\s|[.,!?;])`, 'i');
111+
maskedMessage = maskedMessage.replace(regex, (match) => {
112+
return match.replace(new RegExp(word, 'i'), '**');
113+
});
114+
});
115+
return maskedMessage;
116+
};
117+
118+
// Function to check message for forbidden words and return line number if applicable
119+
const getLineNumber = (message: string, blacklist: string[]): number | null => {
120+
const lines = message.split('\n'); // Split message into lines
121+
for (let i = 0; i < lines.length; i++) {
122+
const line = lines[i];
123+
const isForbidden = blacklist.some((word) => {
124+
const regex = new RegExp(`(^|\\s)${word}($|\\s|[.,!?;])`, 'i');
125+
return regex.test(line);
126+
});
127+
if (isForbidden) {
128+
return i + 1; // Return the 1-based index of the line
129+
}
130+
}
131+
return null; // Return null if no forbidden words are found
132+
};
133+
// Sending the warning with the line number and masked message
134+
const lineNumber = getLineNumber(messageText!, blacklist);
135+
const maskedMessage = maskForbiddenWords(messageText!, blacklist);
136+
137+
let warningMessage = `
138+
⚠️ Warning, ${info_user}!
139+
140+
You have used a forbidden word from the blacklist in your message. As a result, you have been temporarily muted for 1 hour.
141+
142+
This action has been taken to maintain a respectful and safe environment for everyone in the group.
143+
144+
Please be mindful of your language and refrain from using inappropriate words. Repeated violations may result in further actions.
145+
146+
Here is the message you sent (with forbidden words masked):
147+
_"${maskedMessage}"_
148+
149+
We appreciate your understanding and cooperation in keeping the community welcoming for all members. Thank you!
150+
`;
151+
152+
if (lineNumber !== null) {
153+
warningMessage += `\n\n⚠️ Forbidden word detected on line ${lineNumber}.`;
154+
}
155+
return warningMessage;
156+
}
157+
}

0 commit comments

Comments
 (0)