Skip to content

Commit 2bf45d1

Browse files
committed
refactor: Refactored admin commands, added middleware, and updated services and config files.
1 parent ad21030 commit 2bf45d1

9 files changed

Lines changed: 403 additions & 94 deletions

File tree

Lines changed: 258 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import { Context } from 'grammy';
1+
import { Context, InlineKeyboard } from 'grammy';
22
import { BotReply } from '../../../utils/chat/BotReply';
33
import { Catch } from '../../../decorators/Catch';
44
import { AdminValidationService } from '../../service/admin/validation';
55
import { ApprovedService } from '../../service/admin/Approved';
6-
6+
import { BanService } from '../../service/admin/Ban';
7+
import { WarnService } from '../../service/admin/Warn';
8+
import { parseDuration, tehranZone } from '../../../utils';
9+
import { MuteService } from '../../service/admin/Mute';
10+
import { AdminService } from '../../service/admin/Admin';
11+
import { BlackListService } from '../../service/admin/Blacklist';
12+
import { ChatInfo } from '../../../utils/chat/ChatInfo';
13+
import { GroupSettingsService } from '../../service/admin/Welcome';
714
export class AdminCommands {
815
/** Approved Commands */
916
@Catch({
@@ -13,7 +20,7 @@ export class AdminCommands {
1320
})
1421
static async approved(ctx: Context) {
1522
const reply = new BotReply(ctx);
16-
const validationResult = await AdminValidationService.validateApprovalContext(ctx);
23+
const validationResult = await AdminValidationService.validateContext(ctx);
1724

1825
if (!validationResult) {
1926
return;
@@ -30,14 +37,14 @@ export class AdminCommands {
3037
static async disapproved(ctx: Context) {
3138
const reply = new BotReply(ctx);
3239
// Validate the context to extract required group and user IDs
33-
const validationResult = await AdminValidationService.validateApprovalContext(ctx);
40+
const validationResult = await AdminValidationService.validateContext(ctx);
3441

3542
if (!validationResult) {
3643
return;
3744
}
3845

3946
const { groupId, userId } = validationResult;
40-
await ApprovedService.updateDisapproved(ctx,groupId, userId);
47+
await ApprovedService.updateDisapproved(ctx, groupId, userId);
4148
await reply.textReply(`User with ID ${userId} has been successfully disapproved for group ${groupId}.`);
4249
}
4350
@Catch({
@@ -47,54 +54,264 @@ export class AdminCommands {
4754
})
4855
static async approvedlist(ctx: Context) {
4956
const reply = new BotReply(ctx);
50-
const chat = ctx.chat;
51-
if (!(chat?.type === 'group' || chat?.type === 'supergroup')) {
52-
await ctx.reply('This command can only be used in groups.');
53-
return;
54-
}
55-
56-
const groupId = chat.id;
57+
const groupId = ctx.chat!.id;
5758
const approvedUsers = await ApprovedService.getApprovedUsers(ctx, groupId);
5859
if (!approvedUsers.length) {
59-
await reply.textReply('No users are approved in this group.');
60+
await reply.textReply('There are currently no approved users in this group.');
6061
return;
6162
}
62-
const approvedListMessage = approvedUsers.map((user) => `ID: ${user.telegram_id},\nFirstname: ${user.first_name}`).join('\n');
63-
await reply.textReply(`Approved users in this group:\n${approvedListMessage}`);
63+
const approvedListMessage = approvedUsers.map((user) => `• **ID:** ${user.telegram_id}\n **Name:** ${user.first_name}`).join('\n\n');
64+
65+
// Send a formatted message with the list of approved users
66+
await reply.markdownReply(`Here is the list of approved users in this group:\n\n${approvedListMessage}`);
6467
}
6568
/** Ban Commands */
66-
static async ban() {}
67-
static async unban() {}
69+
static async ban(ctx: Context) {
70+
const reply = new BotReply(ctx);
71+
const groupId = ctx.chat!.id;
72+
const isBanUser = await BanService.ban(ctx);
73+
if (isBanUser) {
74+
await ctx.api.banChatMember(groupId, ctx.message?.reply_to_message?.from?.id!);
75+
await reply.textReply(`User ${ctx.message?.reply_to_message?.from!.first_name} has been removed from the group and their information has been deleted.`);
76+
return;
77+
} else {
78+
await reply.textReply('User or group not found.');
79+
return;
80+
}
81+
}
82+
static async unban(ctx: Context) {
83+
const reply = new BotReply(ctx);
84+
const unBanUser = await BanService.unBan(ctx);
85+
if (unBanUser) {
86+
await reply.textReply(`User ${ctx.message?.reply_to_message?.from?.first_name} has been unbanned and their information has been restored.`);
87+
return;
88+
} else {
89+
await reply.textReply('Please reply to a message from the user you want to unban.');
90+
return;
91+
}
92+
}
6893
/** Warn Commands */
69-
static async warn() {}
70-
static async rmwarn() {}
71-
static async warns() {}
72-
static async warnslist() {}
94+
static async warn(ctx: Context) {
95+
const reply = new BotReply(ctx);
96+
const user = ctx.message?.reply_to_message?.from!;
97+
const { isWarningLimitReached, warningApplied, warnings } = await WarnService.warnUser(ctx);
98+
if (isWarningLimitReached && warningApplied) {
99+
await reply.textReply(`User ${user.first_name} has been muted for 1 day due to excessive warnings.`);
100+
return;
101+
} else if (!isWarningLimitReached && warningApplied) {
102+
await reply.textReply(`User ${user.first_name} has been warned. They now have ${warnings} warnings.`);
103+
return;
104+
} else {
105+
await reply.textReply('User or group not found.');
106+
return;
107+
}
108+
}
109+
static async rmwarn(ctx: Context) {
110+
const reply = new BotReply(ctx);
111+
const user = ctx.message?.reply_to_message?.from!;
112+
const { warningRemoved, warnings } = await WarnService.removeWarn(ctx);
113+
114+
if (warningRemoved) {
115+
await reply.textReply(`User ${user.first_name} now has ${warnings} warnings after the removal.`);
116+
} else {
117+
await reply.textReply('User or group not found or no warnings to remove.');
118+
}
119+
}
120+
static async warns(ctx: Context) {
121+
const reply = new BotReply(ctx);
122+
const user = ctx.message?.reply_to_message?.from!;
123+
124+
const { warnings } = await WarnService.getUserWarnById(ctx);
125+
126+
if (warnings >= 0) {
127+
await reply.textReply(`User ${user.first_name} currently has ${warnings} warnings.`);
128+
} else {
129+
await reply.textReply('User not found.');
130+
}
131+
}
132+
static async warnslist(ctx: Context) {
133+
const reply = new BotReply(ctx);
134+
const warns = await WarnService.getAllWarns(ctx);
135+
await reply.markdownReply(`${warns}`);
136+
}
73137
/** Mute Commands */
74-
static async mute() {}
75-
static async unmute() {}
76-
static async mutelist() {}
138+
static async mute(ctx: Context) {
139+
const reply = new BotReply(ctx);
140+
const message = await MuteService.muteUser(ctx);
141+
return await reply.textReply(message);
142+
}
143+
static async unmute(ctx: Context) {
144+
const reply = new BotReply(ctx);
145+
const message = await MuteService.unmuteUser(ctx);
146+
return await reply.textReply(message);
147+
}
77148
/** Admin Command */
78-
static async grant() {}
79-
static async revoke() {}
149+
static async grant(ctx: Context) {
150+
const reply = new BotReply(ctx);
151+
const grantUser = await AdminService.grant(ctx);
152+
await reply.textReply(grantUser);
153+
}
154+
static async revoke(ctx: Context) {
155+
const reply = new BotReply(ctx);
156+
const revokeUser = await AdminService.revoke(ctx);
157+
await reply.textReply(revokeUser);
158+
}
159+
@Catch({
160+
category: 'BlackList',
161+
message: 'Failed to retrieve or send the blacklist.',
162+
statusCode: 500,
163+
})
80164
/** BlackList Command */
81-
static async blacklist() {}
82-
static async rmbl() {}
83-
static async abl() {}
84-
/** Rules Commands */
85-
/** add-rule|edit-rule|delete-rule|delete_last-rule|view-rule| */
86-
static async rules() {}
165+
static async blacklist(ctx: Context) {
166+
const reply = new BotReply(ctx);
167+
const userId = ctx.from?.id!;
168+
const groupId = ctx.chat?.id!;
169+
const admins = await ctx.getChatAdministrators();
170+
const isAdmin = admins.some((admin) => admin.user.id === userId);
171+
if (!isAdmin) {
172+
await reply.textReply('You need to be an admin to view the blacklist.');
173+
return;
174+
}
175+
await reply.textReply('I have sent you a message in your private chat with the blacklist.');
176+
const blackList = await BlackListService.getAll(ctx, groupId);
177+
if (blackList.length === 0) {
178+
await ctx.api.sendMessage(userId, 'The blacklist is currently empty.');
179+
} else {
180+
await ctx.api.sendMessage(userId, `Blacklist:\n${blackList.join('\n')}`);
181+
}
182+
return;
183+
}
184+
@Catch({
185+
category: 'BlackList',
186+
message: 'Failed to add word to the blacklist.',
187+
statusCode: 400,
188+
})
189+
/** Add a Word to the Blacklist */
190+
static async abl(ctx: Context) {
191+
const reply = new BotReply(ctx);
192+
await ctx.deleteMessage();
193+
const groupId = ctx.chat?.id!;
194+
const word = ctx.message?.text?.split(' ')[1];
195+
if (!word) {
196+
await reply.textReply('Please specify a word to add to the blacklist.');
197+
return;
198+
}
199+
await BlackListService.add(groupId, word);
200+
await reply.send('Blacklist has been updated.');
201+
}
202+
@Catch({
203+
category: 'BlackList',
204+
message: 'Failed to remove word from the blacklist.',
205+
statusCode: 400,
206+
})
207+
/** Remove the Last Word from the Blacklist */
208+
static async rmbl(ctx: Context) {
209+
const reply = new BotReply(ctx);
210+
await ctx.deleteMessage();
211+
const groupId = ctx.chat?.id!;
212+
const wordToRemove = ctx.message?.text?.split(' ')[1];
213+
await BlackListService.remove(groupId, wordToRemove);
214+
await reply.send('Blacklist has been updated.');
215+
}
216+
@Catch({
217+
category: 'BlackList',
218+
message: 'Failed to Clear the Entire Blacklist.',
219+
statusCode: 400,
220+
})
221+
/** Clear the Entire Blacklist */
222+
static async clrbl(ctx: Context) {
223+
await ctx.deleteMessage();
224+
const reply = new BotReply(ctx);
225+
const groupId = ctx.chat?.id!;
226+
await BlackListService.clear(groupId);
227+
await reply.send('The blacklist has been cleared.');
228+
}
87229
/** Pin Command */
88-
static async pin() {}
89-
static async unpin() {}
230+
static async pin(ctx: Context) {
231+
const reply = new BotReply(ctx);
232+
const groupId = ctx.chat?.id!;
233+
const messageId = ctx.message?.reply_to_message?.message_id!;
234+
await ctx.api.pinChatMessage(groupId, messageId);
235+
await reply.textReply('The message has been pinned.');
236+
}
237+
static async unpin(ctx: Context) {
238+
const reply = new BotReply(ctx);
239+
const groupId = ctx.chat?.id!;
240+
const messageId = ctx.message?.reply_to_message?.message_id!;
241+
await ctx.api.unpinChatMessage(groupId, messageId);
242+
await reply.textReply('The pinned message has been unpinned.');
243+
}
90244
/** Purge Command */
91-
static async purge() {}
245+
static async purge(ctx: Context) {
246+
const reply = new BotReply(ctx);
247+
const chatId = ctx.chat?.id;
248+
const replyToMessageId = ctx.message?.reply_to_message?.message_id;
249+
if (!chatId || !replyToMessageId) {
250+
return reply.send('Please reply to a message and use the /purge command.');
251+
}
252+
253+
let lastMessageId = ctx.message?.message_id;
254+
if (!lastMessageId) {
255+
return reply.send('No message ID found.');
256+
}
257+
258+
const countMessages = lastMessageId - replyToMessageId;
259+
if (countMessages <= 0) {
260+
return reply.send('The reply-to message is not older than the current message or no messages to delete.');
261+
}
262+
263+
const messagesToDelete = [];
264+
for (let i = 0; i <= countMessages; i++) {
265+
messagesToDelete.push(replyToMessageId + i);
266+
}
267+
268+
const deleteMessagesInBatches = async (messages: number[]) => {
269+
const batchSize = 100;
270+
for (let i = 0; i < messages.length; i += batchSize) {
271+
const batch = messages.slice(i, i + batchSize);
272+
await ctx.deleteMessages(batch);
273+
}
274+
};
275+
276+
await deleteMessagesInBatches(messagesToDelete);
277+
await reply.send('Deleting done.');
278+
}
92279
/** Group Setting Command */
93-
static async lock() {}
94-
static async unlock() {}
95-
static async title() {}
96-
static async welcome() {}
280+
static async welcome(ctx: Context) {
281+
const reply = new BotReply(ctx);
282+
const input = ctx.message?.text!.split(/\s+/).slice(1);
283+
const action = input![0]?.toLowerCase();
284+
const welcomeContent = input!.join(' ');
285+
if (!action) {
286+
// Retrieve the current welcome message from the database (or service)
287+
const currentWelcome = await GroupSettingsService.getWelcomeMessage(ctx, welcomeContent);
288+
if (currentWelcome) {
289+
await reply.textReply(`Current welcome message: \n${currentWelcome}`);
290+
} else {
291+
await reply.textReply('No welcome message set for this group.');
292+
}
293+
return;
294+
}
295+
296+
// Case to remove the welcome message
297+
if (action === 'r') {
298+
// Delete the current welcome message from the database (or service)
299+
const result = await GroupSettingsService.removeWelcomeMessage(ctx);
300+
if (result) {
301+
await reply.textReply('The welcome message has been removed.');
302+
} else {
303+
await reply.textReply('No welcome message to remove.');
304+
}
305+
return;
306+
}
307+
if (welcomeContent) {
308+
console.log('welcomeContent', welcomeContent);
309+
await GroupSettingsService.setWelcomeMessage(ctx, welcomeContent);
310+
await reply.textReply(`The welcome message has been updated to: \n${welcomeContent}`);
311+
return;
312+
}
97313

98-
/** General Commands */
99-
static async group_stats() {}
314+
// Default response if no valid action is detected
315+
await reply.textReply('Invalid usage of the /welcome command. Use "/welcome" to view the current message, "/welcome r" to remove it, or "/welcome [message]" to set a new welcome message.');
316+
}
100317
}

src/bot/commands/genearl/GeneralCommands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Context } from 'grammy';
2-
import { Catch } from '../../../decorators/ErrorHandlingDecorator';
2+
import { Catch } from '../../../decorators/Catch';
33
import { BotReply } from '../../../utils/chat/BotReply';
44
import { help, start, commands } from '../../../utils/jsons/botMessages.json';
55
import { ChatInfo } from '../../../utils/chat/ChatInfo';
6-
import { DateCommand } from './date';
6+
import { DateCommand } from '../../service/general/date';
77
import { info, user_support } from '../../../../docs/BotInfo.json';
88
/**
99
* Reason for lowercase command names:

0 commit comments

Comments
 (0)