diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 96302ab..00d4254 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -6,27 +6,53 @@ import { fmt } from "@/utils/format" import { ephemeral } from "@/utils/messages" import { getTelegramId } from "@/utils/telegram-id" import { numberOrString, type Role } from "@/utils/types" -import { getUser } from "@/utils/users" +import { getOverloadUser, getUser } from "@/utils/users" export const ban = new CommandsCollection("Banning") .createCommand({ trigger: "ban", - args: [{ key: "reason", optional: true, description: "Optional reason to ban the user" }], + args: [ + { + key: "reasonOrUser", + optional: true, + description: + "If the message is a reply, this argument is the reason. Otherwise, it's the username or user id of the user to ban", + type: numberOrString, + }, + { key: "reason", optional: true, description: "Optional reason to ban the user" }, + ], description: "Permanently ban a user from a group", scope: "group", - reply: "required", + reply: "optional", permissions: { allowedRoles: ["owner", "direttivo"], excludedRoles: ["creator"], allowGroupAdmins: true, }, handler: async ({ args, context, repliedTo }) => { - if (!repliedTo.from) { - logger.error("ban: no repliedTo.from field (the msg was sent in a channel)") + const userOverload = await getOverloadUser(context, repliedTo, args.reasonOrUser, args.reason) + if (userOverload.isErr()) { + await ephemeral( + context.reply( + repliedTo + ? fmt(({ n }) => n`There was an error`) + : fmt(({ n }) => n`Target user not found, please try replying to their message`) + ) + ) + logger.error({ args, repliedTo }, `BAN: ${userOverload.error}`) return } - const res = await Moderation.ban(repliedTo.from, context.chat, context.from, null, [repliedTo], args.reason) + const { user, reason } = userOverload.value + + const res = await Moderation.ban( + user, + context.chat, + context.from, + null, + repliedTo ? [repliedTo] : undefined, + reason + ) if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) }, }) diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 837d662..00ac7f1 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -6,7 +6,7 @@ import { fmt } from "@/utils/format" import { ephemeral } from "@/utils/messages" import { getTelegramId } from "@/utils/telegram-id" import { numberOrString, type Role } from "@/utils/types" -import { getUser } from "@/utils/users" +import { getOverloadUser, getUser } from "@/utils/users" export const mute = new CommandsCollection("Muting") .createCommand({ @@ -47,22 +47,48 @@ export const mute = new CommandsCollection("Muting") }) .createCommand({ trigger: "mute", - args: [{ key: "reason", optional: true, description: "Optional reason to mute the user" }], + args: [ + { + key: "reasonOrUser", + optional: true, + description: + "If the message is a reply, this argument is the reason. Otherwise, it's the username or user id of the user to mute", + type: numberOrString, + }, + { key: "reason", optional: true, description: "Optional reason to mute the user" }, + ], description: "Permanently mute a user from a group", scope: "group", - reply: "required", + reply: "optional", permissions: { allowedRoles: ["owner", "direttivo"], excludedRoles: ["creator"], allowGroupAdmins: true, }, handler: async ({ args, context, repliedTo }) => { - if (!repliedTo.from) { - logger.error("mute: no repliedTo.from field (the msg was sent in a channel)") + const userOverload = await getOverloadUser(context, repliedTo, args.reasonOrUser, args.reason) + if (userOverload.isErr()) { + await ephemeral( + context.reply( + repliedTo + ? fmt(({ n }) => n`There was an error`) + : fmt(({ n }) => n`Target user not found, please try replying to their message`) + ) + ) + logger.error({ args, repliedTo }, `MUTE: ${userOverload.error}`) return } - const res = await Moderation.mute(repliedTo.from, context.chat, context.from, null, [repliedTo], args.reason) + const { user, reason } = userOverload.value + + const res = await Moderation.mute( + user, + context.chat, + context.from, + null, + repliedTo ? [repliedTo] : undefined, + reason + ) if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) }, }) diff --git a/src/utils/users.ts b/src/utils/users.ts index a5b3fbd..93a34ea 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,6 +1,9 @@ import type { Context } from "grammy" -import type { User } from "grammy/types" +import type { Message, User } from "grammy/types" +import { Err, Ok, type Result } from "neverthrow" +import { logger } from "@/logger" import { MessageUserStorage } from "@/middlewares/message-user-storage" +import { getTelegramId } from "./telegram-id" export async function getUser(userId: number, ctx: C | null): Promise { // TODO: check if this works correctly @@ -26,3 +29,31 @@ export function printCtxFrom(ctx: C): string { if (!ctx.from) return "" return printUsername(ctx.from) } + +export async function getOverloadUser( + context: C, + repliedTo: Message | null, + firstArg?: string | number, + secondArg?: string +): Promise> { + if (repliedTo) { + if (!repliedTo.from) { + // error + return new Err("[getOverloadUser] no repliedTo.from field (the msg was sent in a channel)") + } + return new Ok({ user: repliedTo.from, reason: [firstArg, secondArg].filter(Boolean).join(" ") }) + } + + if (!firstArg) return new Err("[getOverloadUser] No firstArg passed (without repliedTo)") + + const userId = typeof firstArg === "number" ? firstArg : await getTelegramId(firstArg).catch(() => null) + if (!userId) return new Err("[getOverloadUser] Cannot retrieve the userId from arg or redis") + + const user = await getUser(userId, context).catch(() => null) + if (!user) return new Err("[getOverloadUser] Cannot retrieve the User from chatMember or storage") + + return new Ok({ + user, + reason: secondArg, + }) +}