Compare commits

...

14 Commits

Author SHA1 Message Date
doryan 7b31cccf31 Now the admin_check function gets a second argument in the form of user_id, and this function is used in try_restrict 2024-06-08 03:12:16 +04:00
doryan 0ec0c94501 Function for checks for the existence of target user have been implemented for enum TargetUser 2024-06-08 03:09:17 +04:00
doryan 89119936c8 Сheck for the existence of target user have been added 2024-06-08 03:08:00 +04:00
doryan ac9fbcf344 Admin checks have been implemented in "Inner middleware" 2024-06-08 03:04:34 +04:00
doryan d803f6931f Unnecessary target checks have been removed 2024-06-08 03:02:53 +04:00
doryan a9ab73ec72 Some corrections to the command help text 2024-06-08 03:00:47 +04:00
doryan 2e02d9df47 Help response text has been moved to html file 2024-06-08 02:58:39 +04:00
doryan 9a64001926 Merge pull request 'Reformat output chat messages' (#1) from dogma/gluon:fix-messages into main
Reviewed-on: #1
2024-06-07 11:47:41 +03:00
Dogma Toscarina 2f0ae08d4f handlers/{actions,command_handlers}: reformat messages
Avoid interactivity, make messages short.
2024-06-07 10:45:49 +03:00
Dogma Toscarina 06b3a473ed middlewares/admin_check_middleware: shorten error message 2024-06-07 10:41:53 +03:00
Dogma Toscarina 6e59ee1eb8 utils/telegram/try_do: shorten DEMOTE_FAILURE_MESSAGE
It would be annoying to get this long message spamming the chat window.

Such error messages should describe what has gone wrong, but not give
any guidances, advices or directions; they belong elsewhere
(documentation, wiki, etc).
2024-06-07 10:41:53 +03:00
Dogma Toscarina 18b938bf95 handlers/dice_handler/dice: make message short and concise. 2024-06-07 10:41:53 +03:00
Dogma Toscarina 9f5174c442 types/enums/time_metrics: fix grammar 2024-06-07 10:41:53 +03:00
Dogma Toscarina c6222d42f8 assets: fix help messages (mute, unmute)
There are quite some problems with the way commands are explained:

* Interactivity - command arguments gets explained as if one is
  following a guide ("insert this, enter that"). Better explain them
  without any "guidance"; use present tense to describe commands.

* Duplicity - the purpose of the command is explained over and over
  when arguments are being described.

* Separation - there is no need to divide required and optional
  command arguments into their respective sections. Their amount did
  not grow up that much for this to make sense.

* mute_command_help.html: i64 would be sufficient for developers, but
  not end users. It does not stop anyone from specifying overflowing
  integer, after all.
2024-06-07 10:41:36 +03:00
16 changed files with 94 additions and 68 deletions

View File

@ -0,0 +1,12 @@
<b>Команды (для админов)</b>
<code>/mute {ID | REPLY} &lt;DURATION&gt; [TIME METRIC]</code> - выдаёт мут на заданное время;
<code>/unmute {ID | REPLY}</code> - снимает мут;
Для более подробного описания, введите команды без аргументов.
<b>Эмодзи (для админов)</b>
🎲 - выдаёт мут пользователю на случайно заданное время (от 1 до 6 дней);
🎰 - выдаёт мут пользователю на случайно заданное время (от 1 до 63 дней), в случае джекпота участник отправляется в бан.

View File

@ -3,6 +3,7 @@ use std::include_str;
pub mod files { pub mod files {
use super::*; use super::*;
pub const HELP_COMMAND_TEXT: &str = include_str!("help_command.html");
pub const MUTE_COMMAND_HELP: &str = include_str!("mute_command_help.html"); pub const MUTE_COMMAND_HELP: &str = include_str!("mute_command_help.html");
pub const UNMUTE_COMMAND_HELP: &str = include_str!("unmute_command_help.html"); pub const UNMUTE_COMMAND_HELP: &str = include_str!("unmute_command_help.html");
} }

View File

@ -1,10 +1,12 @@
<code>/mute [ID | REPLY] &lt;DURATION: i64 (long long int)&gt; [TIME METRIC]</code> <code>/mute {ID | REPLY} &lt;DURATION&gt; [TIME METRIC]</code>
<b>Обязательные критерий:</b> <b>Выдаёт мут.</b>
<b><em>1. Участник чата</em></b>. Введите ID участника чата, либо ответьте на его сообщение, чтобы его замьютить; Использовать при ответе на сообщение или указать ID пользователя.
<b><em>2. Длительность</em></b>. Введите длительность мьюта, длительность должна быть не меньше нуля.
<b>Необязательные критерий:</b> <b><em>1. ID | REPLY.</em></b> ID или ответ на сообщение пользователя;
<b><em>2. DURATION.</em></b> Продолжительность мута (DURATION &gt; 0);
<b><em>3. TIME METRIC.</em></b> Временная метрика. Измеряется в минутах/часах/днях/неделях/месяцах. По умолчанию применяется в днях.
<b><u>Команда доступна только администраторам.</u></b>
<b><em>1. Временная метрика.</em></b> Если она не указана, то автоматически участнику чата выдаётся мьют в днях.

View File

@ -1,6 +1,8 @@
<code>/unmute [ID | REPLY]</code> <code>/unmute {ID | REPLY}</code>
<b>Обязательные критерий:</b> <b>Cнимает мут с пользователя.</b>
<b><em>1. Участник чата</em></b>. Введите ID участника чата, либо ответьте на его сообщение, чтобы c него снять мьют. <b><em>1. ID | REPLY.</em></b> ID или ответ на сообщение пользователя.
<b><u>Команда доступна только администраторам.</u></b>

View File

@ -25,7 +25,7 @@ pub async fn ban_member(
Some(id) => id, Some(id) => id,
None => { None => {
sender_builder sender_builder
.text("Ответьте на сообщение участника чата, которого вы хотите забанить") .text("Нет ID или ответа на сообщение пользователя.")
.reply_to(message.id()) .reply_to(message.id())
.build() .build()
.send(&bot) .send(&bot)
@ -39,21 +39,19 @@ pub async fn ban_member(
let callback = || async { ban_chat_member(&bot, user_id, chat_id).await }; let callback = || async { ban_chat_member(&bot, user_id, chat_id).await };
let demote_args: (&Bot, i64, i64) = (&bot, user_id, chat_id); let demote_args: (&Bot, i64, i64) = (&bot, user_id, chat_id);
sender_builder sender_builder.set_text("Невозможно забанить пользователя.");
.set_text("Невозможно забанить участника чата, демотните своими силами и попробуйте снова");
if try_restrict(callback, demote_args, sender_builder.clone().build()) if try_restrict(callback, demote_args, sender_builder.clone().build())
.await .await
.is_err() .is_err()
{ {
sender_builder.build().send(&bot).await?;
Ok(EventReturn::Cancel) Ok(EventReturn::Cancel)
} else { } else {
let banned_user_name: String = user.get_user_name(&bot, &message).await.unwrap(); let banned_user_name: String = user.get_user_name(&bot, &message).await.unwrap();
sender_builder sender_builder
.reply_to(message.id()) .reply_to(message.id())
.text(format!("Пользователь {} забанен.", banned_user_name)) .text(format!("Пользователь {banned_user_name} забанен."))
.build() .build()
.send(&bot) .send(&bot)
.await?; .await?;

View File

@ -25,19 +25,7 @@ pub async fn mute_member(
) -> HandlerResult { ) -> HandlerResult {
let (bot, message, mut sender_builder): ExtractedEntityData = handler_entity.extract(); let (bot, message, mut sender_builder): ExtractedEntityData = handler_entity.extract();
let user_id: i64 = match user.get_id() { let user_id: i64 = user.get_id().unwrap();
Some(id) => id,
None => {
sender_builder
.text("Ответьте на сообщение участника чата, которого вы хотите замьютить")
.reply_to(message.id())
.build()
.send(&bot)
.await
.unwrap();
return Ok(EventReturn::Cancel);
}
};
sleep(Duration::from_millis(time.1)).await; sleep(Duration::from_millis(time.1)).await;
@ -46,13 +34,12 @@ pub async fn mute_member(
let callback = || async { restrict(&bot, user_id, unmute_date, chat_id).await }; let callback = || async { restrict(&bot, user_id, unmute_date, chat_id).await };
sender_builder.set_text("Невозможно замьютить участника чата, демотните и попробуйте снова"); sender_builder.set_text("Невозможно выдать мут.");
if try_restrict(callback, demote_args, sender_builder.clone().build()) if try_restrict(callback, demote_args, sender_builder.clone().build())
.await .await
.is_err() .is_err()
{ {
sender_builder.build().send(&bot).await?;
Ok(EventReturn::Cancel) Ok(EventReturn::Cancel)
} else { } else {
let muted_user_name: String = user.get_user_name(&bot, &message).await.unwrap(); let muted_user_name: String = user.get_user_name(&bot, &message).await.unwrap();
@ -60,8 +47,7 @@ pub async fn mute_member(
sender_builder sender_builder
.reply_to(message.id()) .reply_to(message.id())
.text(format!( .text(format!(
"Пользователь {} замьючен на {:?} {}.", "Пользователю {muted_user_name} выдан мут на {mute_duration} {postfix}."
muted_user_name, mute_duration, postfix
)) ))
.build() .build()
.send(&bot) .send(&bot)

View File

@ -28,9 +28,7 @@ pub async fn unmute_member(
if let Err(error) = bot.send(bot_action).await { if let Err(error) = bot.send(bot_action).await {
sender_builder sender_builder
.text(format!( .text(format!("Невозможно снять мут с пользователя: {error:?}."))
"Невозможно снять мьют с участника чата по причине: {error:?}"
))
.build() .build()
.send(&bot) .send(&bot)
.await?; .await?;
@ -40,7 +38,7 @@ pub async fn unmute_member(
sender_builder sender_builder
.reply_to(message.id()) .reply_to(message.id())
.text(format!("С пользователя {} был снят мьют.", muted_user_name)) .text(format!("С пользователя {muted_user_name} снят мут."))
.build() .build()
.send(&bot) .send(&bot)
.await .await

View File

@ -1,25 +1,16 @@
use telers::{ use telers::{
enums::parse_mode::ParseMode,
event::{telegram::HandlerResult, EventReturn}, event::{telegram::HandlerResult, EventReturn},
types::Message, types::Message,
Bot, Bot,
}; };
use crate::types::structs::message_sender::MessageSender; use crate::{assets::files::HELP_COMMAND_TEXT, types::structs::message_sender::MessageSender};
const HELP_TEXT: &str = "\
/help - помощь по боту.\n\
/unmute - снимает с участника чата мьют, для подробностей, введите команду без аргументов \
(только для админов).\n\
/mute - накладывает на участника чата мьют, для подробностей, введите команду без аргументов \
(только для админов).\n\
🎲 - выдаёт мут, для этого нужно отправить ТОЛЬКО эмодзи в ответ на сообщение участника. \
чата, которого вы хотите замьютить (только для админов).\n\
🎰 - выдаёт бан в случае джекпота, напротив, мьют, всё так же кидайте этот эмодзи в ответ \
на сообщение участника чата, которого вы хотите замьютить/забанить (только для админов).";
pub async fn help(bot: Bot, message: Message) -> HandlerResult { pub async fn help(bot: Bot, message: Message) -> HandlerResult {
MessageSender::builder(message.chat().id()) MessageSender::builder(message.chat().id())
.text(HELP_TEXT) .text(HELP_COMMAND_TEXT)
.parse_mode(ParseMode::HTML)
.build() .build()
.send(&bot) .send(&bot)
.await .await

View File

@ -35,8 +35,9 @@ pub async fn mute(bot: Bot, message: Message, command: CommandObject) -> Handler
if let Ok(id) = raw_id.parse::<i64>() { if let Ok(id) = raw_id.parse::<i64>() {
TargetUser::Id(id) TargetUser::Id(id)
} else { } else {
handler_entity.message_sender_builder handler_entity
.text("Ответьте на сообщение или укажите первым аргументом ID человека, которого вы хотите замьютить") .message_sender_builder
.text("Нет ID или ответа на сообщение пользователя.")
.build() .build()
.send(&handler_entity.bot_instance) .send(&handler_entity.bot_instance)
.await?; .await?;
@ -59,7 +60,7 @@ pub async fn mute(bot: Bot, message: Message, command: CommandObject) -> Handler
handler_entity handler_entity
.message_sender_builder .message_sender_builder
.set_text("Укажите число, характеризующее длительность мьюта."); .set_text("Не указана длительность мута.");
match args.get(duration_argument_position).cloned() { match args.get(duration_argument_position).cloned() {
Some(duration_str) => { Some(duration_str) => {

View File

@ -27,7 +27,7 @@ pub async fn unmute(bot: Bot, message: Message, command: CommandObject) -> Handl
Some(raw_id) => { Some(raw_id) => {
handler_entity handler_entity
.message_sender_builder .message_sender_builder
.set_text("Укажите id пользователя, с которого вы хотите снять мьют"); .set_text("Нет ID или ответа на сообщение пользователя.");
if let Ok(parsed_id) = raw_id.parse::<i64>() { if let Ok(parsed_id) = raw_id.parse::<i64>() {
let on_id: TargetUser = TargetUser::Id(parsed_id); let on_id: TargetUser = TargetUser::Id(parsed_id);

View File

@ -22,8 +22,20 @@ pub async fn dice_handler(bot: Bot, message: Message) -> HandlerResult {
let handler_entity: HandlerEntity = HandlerEntity::new(bot, message, sender); let handler_entity: HandlerEntity = HandlerEntity::new(bot, message, sender);
let (mute_time, emoji): (TimeMetrics, &str) = (TimeMetrics::Days(dice.value), &dice.emoji); let (mute_time, emoji): (TimeMetrics, &str) = (TimeMetrics::Days(dice.value), &dice.emoji);
let target: TargetUser = TargetUser::Reply(handler_entity.message_reciever.clone()); let target: TargetUser = TargetUser::Reply(handler_entity.message_reciever.clone());
if !target.exist() {
handler_entity
.message_sender_builder
.text("Нет ответа на сообщение пользователя.")
.build()
.send(&handler_entity.bot_instance)
.await?;
return Ok(EventReturn::Cancel);
}
match emoji { match emoji {
"🎲" => { "🎲" => {
mute_member(handler_entity, chat_id, target, (mute_time, DICE_DELAY_MS)).await?; mute_member(handler_entity, chat_id, target, (mute_time, DICE_DELAY_MS)).await?;
@ -44,7 +56,7 @@ pub async fn dice_handler(bot: Bot, message: Message) -> HandlerResult {
_ => { _ => {
handler_entity handler_entity
.message_sender_builder .message_sender_builder
.text("Такой эмодзи не имеет привязки к какому либо действию бота.") .text("Эмодзи не имеет привязанных действий.")
.build() .build()
.send(&handler_entity.bot_instance) .send(&handler_entity.bot_instance)
.await?; .await?;

View File

@ -30,13 +30,12 @@ impl InnerMiddleware for AdminCheck {
let chat_id: i64 = message.chat_id().unwrap(); let chat_id: i64 = message.chat_id().unwrap();
if is_admin(&admins_list, message.from().unwrap()) { if is_admin(&admins_list, message.from().unwrap().id) {
let response = next(request).await?; let response = next(request).await?;
return Ok(response); return Ok(response);
} else { } else {
println!("lol");
MessageSender::builder(chat_id) MessageSender::builder(chat_id)
.text("У ваc нет прав администратора, чтобы использовать эту команду.") .text("Недостаточно прав для использования данной команды.")
.build() .build()
.send(&bot) .send(&bot)
.await .await

View File

@ -15,6 +15,21 @@ pub enum TargetUser {
} }
impl TargetUser { impl TargetUser {
pub fn exist(&self) -> bool {
match self {
Self::Id(_id) => true,
Self::Reply(msg) => {
if let Some(replied_msg) = msg.reply_to_message() {
return replied_msg.from().map(|user| user.id).is_some();
} else {
false
}
}
Self::CurrentMessage(msg) => msg.from().map(|user| user.id).is_some(),
Self::None => false,
}
}
pub fn get_id(&self) -> Option<i64> { pub fn get_id(&self) -> Option<i64> {
match self { match self {
Self::Id(id) => Some(*id), Self::Id(id) => Some(*id),

View File

@ -21,7 +21,7 @@ impl TimeMetrics {
"w" | "weeks" | "week" | "недель" | "недели" | "неделя" | "н" => { "w" | "weeks" | "week" | "недель" | "недели" | "неделя" | "н" => {
Self::Weeks(duration) Self::Weeks(duration)
} }
"m" | "mounths" | "mounth" | "месяц" | "месяца" | "месяцев" | "мес" => { "m" | "months" | "month" | "месяц" | "месяца" | "месяцев" | "мес" => {
Self::Mounths(duration) Self::Mounths(duration)
} }
_ => Self::Days(duration), _ => Self::Days(duration),

View File

@ -1,11 +1,11 @@
use telers::types::{chat_member::ChatMember, User}; use telers::types::chat_member::ChatMember;
pub fn is_admin(all_admin_members: &Vec<ChatMember>, user: &User) -> bool { pub fn is_admin(all_admin_members: &Vec<ChatMember>, user_id: i64) -> bool {
all_admin_members all_admin_members
.iter() .iter()
.any(|admin: &ChatMember| match admin { .any(|admin: &ChatMember| match admin {
ChatMember::Administrator(admin) => &admin.user == user, ChatMember::Administrator(admin) => admin.user.id == user_id,
ChatMember::Owner(owner) => &owner.user == user, ChatMember::Owner(owner) => owner.user.id == user_id,
_ => false, _ => false,
}) })
} }

View File

@ -1,15 +1,12 @@
use telers::{errors::SessionErrorKind as ErrorKind, event::EventReturn, Bot}; use telers::{errors::SessionErrorKind as ErrorKind, event::EventReturn, types::ChatMember, Bot};
use crate::types::structs::message_sender::MessageSender; use crate::types::structs::message_sender::MessageSender;
use std::future::Future; use std::future::Future;
use super::demote::demote_user; use super::{admin_check::is_admin, demote::demote_user, get_all_admins::get_all_admins};
const DEMOTE_FAILURE_MESSAGE: &str = "\ const DEMOTE_FAILURE_MESSAGE: &str = "Команда не может быть выполнена: \
Нельзя выдать ограничение пользователю, т.к. невозможно демотнуть \ не удалось удалить административные привилегии пользователя.";
участника посредством бота, если ему выдан админ при помощи других \
админов или владельца чата.\
";
pub async fn try_restrict<F, R>( pub async fn try_restrict<F, R>(
future_callback: F, future_callback: F,
@ -22,6 +19,18 @@ where
{ {
let (bot, user_id, chat_id): (&Bot, i64, i64) = demote_args; let (bot, user_id, chat_id): (&Bot, i64, i64) = demote_args;
let admins: Vec<ChatMember> = get_all_admins(bot, chat_id).await.unwrap();
if is_admin(&admins, user_id) {
MessageSender::builder(chat_id)
.text("Нельзя применить эту команду на администраторе или бота.")
.build()
.send(bot)
.await
.unwrap();
return Err(EventReturn::Cancel);
}
if future_callback().await.is_err() { if future_callback().await.is_err() {
if demote_user(bot, user_id, chat_id).await.is_err() { if demote_user(bot, user_id, chat_id).await.is_err() {
MessageSender::builder(chat_id) MessageSender::builder(chat_id)