reinit git

This commit is contained in:
doryan 2024-05-31 00:37:47 +04:00
commit 7ec67cb6e4
34 changed files with 723 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
.env
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "dorya"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.86"
async-trait = "0.1.80"
chrono = "0.4.38"
dashmap = "5.5.3"
dotenvy = "0.15.7"
futures = "0.3.30"
telers = "1.0.0-alpha.18"
tokio = { version="1.37.0", features=["rt-multi-thread"] }
tracing = "0.1.40"
tracing-subscriber = {version="0.3.18", features=["env-filter"]}

View File

@ -0,0 +1,64 @@
use telers::{
event::{telegram::HandlerResult, EventReturn},
types::{Message, User},
Bot,
};
use tokio::time::{sleep, Duration as DurationSleep};
use crate::{
types::structs::{bot_entity::BotEntity, message_sender::MessageSender},
utils::telegram::try_do_action::try_restrict,
};
use crate::utils::telegram::ban_member::ban_chat_member;
pub async fn ban(bot_entity: BotEntity, delay: u64) -> HandlerResult {
let (bot, message, sender): (Bot, Message, MessageSender) = (
bot_entity.bot_instance,
bot_entity.receive_message,
bot_entity.message_sender,
);
let reply_to: Message;
let chat_id: i64 = message.chat().id();
if let Some(msg) = message.reply_to_message() {
reply_to = msg.clone();
} else {
sender
.reply_to(message.id())
.text("Ответьте на сообщение, чтобы забанить.")
.send(&bot)
.await?;
return Ok(EventReturn::Cancel);
}
let user_data: &User = reply_to.from().unwrap();
let user_id: i64 = user_data.id;
sleep(DurationSleep::from_millis(delay)).await;
let future = || async { ban_chat_member(&bot, user_id, chat_id).await };
let demote_args: (&Bot, i64, i64) = (&bot, user_id, chat_id);
let failure_sender: MessageSender = sender
.clone()
.text("Невозможно забанить участника чата, демотните и попробуйте снова");
if try_restrict(future, demote_args, failure_sender)
.await
.is_err()
{
Ok(EventReturn::Cancel)
} else {
let banned_user_name: String = reply_to.from().unwrap().clone().username.unwrap();
sender
.reply_to(message.id())
.text(format!("Пользователь {} забанен.", banned_user_name))
.send(&bot)
.await?;
Ok(EventReturn::Finish)
}
}

View File

@ -0,0 +1,2 @@
pub mod ban;
pub mod mute;

View File

@ -0,0 +1,80 @@
use futures::stream::futures_unordered;
use telers::{
event::{telegram::HandlerResult, EventReturn},
types::{Message, User},
Bot,
};
use tokio::time::{sleep, Duration as DurationSleep};
use crate::{
types::{
enums::time_metrics::TimeMetrics,
structs::{
bot_entity::BotEntity, countable_time::CountableTime, message_sender::MessageSender,
},
traits::countable_interface::ICountable,
},
utils::telegram::try_do_action::try_restrict,
};
use crate::utils::{general::unrestrict_date::unrestrict_date, telegram::restrict::restrict};
pub async fn mute(bot_entity: BotEntity, values: (TimeMetrics, u64)) -> HandlerResult {
let (bot, message, sender): (Bot, Message, MessageSender) = (
bot_entity.bot_instance,
bot_entity.receive_message,
bot_entity.message_sender,
);
let reply_to: Message;
let chat_id: i64 = message.chat().id();
if let Some(msg) = message.reply_to_message() {
reply_to = msg.clone();
} else {
sender
.reply_to(message.id())
.text("Ответьте на сообщение, чтобы замьютить.")
.send(&bot)
.await?;
return Ok(EventReturn::Cancel);
}
let user_data: &User = reply_to.from().unwrap();
let user_id: i64 = user_data.id;
sleep(DurationSleep::from_millis(values.1)).await;
let time_duration = values.0.extract();
let unmute_date = unrestrict_date(time_duration);
let postfix = CountableTime::from_value(time_duration)
.get_postfix(values.0)
.unwrap();
let future = || async { restrict(&bot, user_id, unmute_date, chat_id).await };
let demote_args: (&Bot, i64, i64) = (&bot, user_id, chat_id);
let failure_sender: MessageSender = sender
.clone()
.text("Невозможно замьютить участника чата, демотните и попробуйте снова");
if try_restrict(future, demote_args, failure_sender)
.await
.is_err()
{
Ok(EventReturn::Cancel)
} else {
let muted_user_name = reply_to.from().unwrap().clone().username.unwrap();
sender
.reply_to(message.id())
.text(format!(
"Пользователь {} замьючен на {:?} {}.",
muted_user_name, time_duration, postfix
))
.send(&bot)
.await?;
Ok(EventReturn::Finish)
}
}

View File

@ -0,0 +1,26 @@
use telers::{
event::{telegram::HandlerResult, EventReturn},
types::Message,
Bot,
};
use crate::types::structs::message_sender::MessageSender;
const HELP_TEXT: &str = "\
/help - помощь по боту.\n\
/unmute - снимает с человека мьют, нужно ответить на сообщение, чтобы команда сработала (\
только для админов).\n\
🎲 - выдаёт мут, для этого нужно отправить ТОЛЬКО эмодзи в ответ на сообщение участника. \
чата, которого вы хотите замьютить (только для админов).\n\
🎰 - выдаёт бан в случае джекпота, напротив, мьют, всё так же кидайте этот эмодзи в ответ \
на сообщение участника чата, которого вы хотите замьютить/забанить (только для админов).";
pub async fn help(bot: Bot, message: Message) -> HandlerResult {
println!("hi");
MessageSender::new(message.chat().id())
.text(HELP_TEXT)
.send(&bot)
.await
.unwrap();
Ok(EventReturn::Finish)
}

View File

@ -0,0 +1,2 @@
pub mod help;
pub mod unmute;

View File

@ -0,0 +1,60 @@
use telers::{
event::{telegram::HandlerResult, EventReturn},
filters::CommandObject,
methods::RestrictChatMember,
types::User,
types::{ChatPermissions, Message},
Bot,
};
use crate::types::structs::message_sender::MessageSender;
pub async fn unmute(bot: Bot, message: Message, command: CommandObject) -> HandlerResult {
let sender = MessageSender::new(message.chat().id());
let reply_to: Message;
let chat_id: i64 = message.chat().id();
if let Some(msg) = message.reply_to_message() {
reply_to = msg.clone();
} else {
sender
.reply_to(message.id())
.text("Ответьте на сообщение, чтобы замьютить.")
.send(&bot)
.await?;
return Ok(EventReturn::Cancel);
}
let user_data: &User = reply_to.from().unwrap();
let user_id: i64 = user_data.id;
let default_member_permissions = ChatPermissions::all()
.can_change_info(false)
.can_manage_topics(false)
.can_invite_users(false)
.can_pin_messages(false);
let bot_action = RestrictChatMember::new(chat_id, user_id, default_member_permissions);
let username = reply_to.from().unwrap().clone().username.unwrap();
if let Err(error) = bot.send(bot_action).await {
sender
.text(format!(
"Невозможно снять мьют с участника чата по причине: {error:?}"
))
.send(&bot)
.await?;
return Ok(EventReturn::Cancel);
} else {
sender
.reply_to(message.id())
.text(format!("С пользователя {} был снят мьут.", username))
.send(&bot)
.await
.unwrap();
}
Ok(EventReturn::Finish)
}

View File

@ -0,0 +1 @@
pub mod endpoints;

View File

@ -0,0 +1,50 @@
use telers::{
event::{telegram::HandlerResult, EventReturn},
types::{Dice, Message},
Bot,
};
use crate::{
handlers::actions::{ban::ban, mute::mute},
types::{
enums::time_metrics::TimeMetrics,
structs::{bot_entity::BotEntity, message_sender::MessageSender},
},
};
const DICE_DELAY_MS: u64 = 4000u64;
const CASINO_DELAY_MS: u64 = 1500u64;
pub async fn dice_handler(bot: Bot, message: Message) -> HandlerResult {
let (chat_id, dice): (i64, Dice) = (message.chat().id(), message.dice().unwrap().clone());
let bot_entity: BotEntity = BotEntity {
bot_instance: bot,
receive_message: message,
message_sender: MessageSender::new(chat_id),
};
let (mute_time, emoji): (TimeMetrics, &str) = (TimeMetrics::Days(dice.value), &dice.emoji);
match emoji {
"🎲" => {
mute(bot_entity, (mute_time, DICE_DELAY_MS)).await?;
}
"🎰" => {
if dice.value == 64 {
ban(bot_entity, CASINO_DELAY_MS).await?;
} else {
mute(bot_entity, (mute_time, CASINO_DELAY_MS)).await?;
}
}
_ => {
bot_entity
.message_sender
.text("Такой эмодзи не имеет привязки к какому либо действию бота.")
.send(&bot_entity.bot_instance)
.await?;
}
}
Ok(EventReturn::Finish)
}

View File

@ -0,0 +1 @@
pub mod dice;

3
src/handlers/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod actions;
pub mod commands_handler;
pub mod dice_handler;

83
src/main.rs Normal file
View File

@ -0,0 +1,83 @@
use dotenvy::dotenv;
use telers::{
enums::ContentType,
event::ToServiceProvider,
filters::{content_type::ContentType as CT, Command},
Bot, Dispatcher, Router,
};
use tracing_subscriber::{fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter};
mod middlewares;
mod types;
mod utils;
use middlewares::admin_check_middleware::AdminCheck;
mod handlers;
use handlers::{
commands_handler::endpoints::{help::help, unmute::unmute},
dice_handler::dice::dice_handler,
};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_env("RUST_LOG"))
.init();
dotenv().ok();
let token_result = dotenvy::var("TOKEN");
match token_result {
Ok(token) => {
let bot = Bot::new(token);
let mut route = Router::new("main");
let mut dice = Router::new("dice");
dice.message
.register(dice_handler)
.filter(CT::one(ContentType::Dice));
dice.message.inner_middlewares.register(AdminCheck {});
let mut command = Router::new("commands");
let mut admin_commands = Router::new("admin_commands");
let mut default_commands = Router::new("default_commands");
admin_commands
.message
.register(unmute)
.filter(Command::one("unmute"));
admin_commands
.message
.inner_middlewares
.register(AdminCheck {});
default_commands
.message
.register(help)
.filter(Command::one("help"));
command.include(admin_commands).include(default_commands);
route.include(dice).include(command);
let dispatcher = Dispatcher::builder().main_router(route).bot(bot).build();
dispatcher
.to_service_provider_default()
.unwrap()
.run_polling()
.await
.unwrap();
}
Err(error) => {
println!("Error text: {:?}", error);
}
}
}

View File

@ -0,0 +1,48 @@
use async_trait::async_trait;
use telers::{
errors::{EventErrorKind, HandlerError},
event::telegram::{HandlerRequest, HandlerResponse},
middlewares::{InnerMiddleware, Next},
};
use anyhow::Error as Reject;
use crate::{
types::structs::message_sender::MessageSender,
utils::telegram::{admin_check::is_admin, get_all_admins::get_all_admins},
};
#[derive(Default)]
pub struct AdminCheck {}
#[async_trait]
impl InnerMiddleware for AdminCheck {
async fn call(
&self,
request: HandlerRequest,
next: Next,
) -> Result<HandlerResponse, EventErrorKind> {
let (bot, message) = (request.clone().bot, request.clone().update);
let admins_list = get_all_admins(&bot, message.chat().unwrap().id())
.await
.unwrap();
let chat_id: i64 = message.chat_id().unwrap();
if is_admin(&admins_list, message.from().unwrap()) {
let response = next(request).await?;
return Ok(response);
} else {
MessageSender::new(chat_id)
.text("У вам нет прав администратора, чтобы использовать эту команду.")
.send(&bot)
.await
.unwrap();
return Err(EventErrorKind::Handler(HandlerError::new(Reject::msg(
"User isn't admin.".to_string(),
))));
}
}
}

1
src/middlewares/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod admin_check_middleware;

1
src/types/enums/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod time_metrics;

View File

@ -0,0 +1,30 @@
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub enum TimeMetrics {
Minutes(i64),
Hours(i64),
Days(i64),
Weeks(i64),
Mounths(i64),
}
impl TimeMetrics {
pub fn extract(self) -> i64 {
match self {
Self::Minutes(min) => min,
Self::Hours(hrs) => hrs,
Self::Days(day) => day,
Self::Weeks(wks) => wks,
Self::Mounths(mon) => mon,
}
}
pub fn get_word_declensions(self) -> (impl Into<String>, impl Into<String>, impl Into<String>) {
match self {
Self::Minutes(_) => ("минута", "минуты", "минут"),
Self::Hours(_) => ("час", "часов", "часов"),
Self::Days(_) => ("день", "дня", "дней"),
Self::Weeks(_) => ("неделя", "недели", "недель"),
Self::Mounths(_) => ("месяц", "месяца", "месяцев"),
}
}
}

3
src/types/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod enums;
pub mod structs;
pub mod traits;

View File

@ -0,0 +1,8 @@
use crate::types::structs::message_sender::MessageSender;
use telers::{types::Message, Bot};
pub struct BotEntity {
pub bot_instance: Bot,
pub receive_message: Message,
pub message_sender: MessageSender,
}

View File

@ -0,0 +1,37 @@
use crate::types::{enums::time_metrics::TimeMetrics, traits::countable_interface::ICountable};
pub struct CountableTime(i64, i64);
impl ICountable<(i64, i64), TimeMetrics> for CountableTime {
fn new() -> Self {
Self(0, 0)
}
fn from_value(value: i64) -> Self {
Self((value / 10) % 10, value % 10)
}
fn get_values(&self) -> (i64, i64) {
(self.0, self.1)
}
fn set_values(&self, value: i64) -> Self {
Self((value / 10) % 10, value % 10)
}
fn get_postfix(&self, metrics: TimeMetrics) -> Option<String> {
let all_word_declensions = metrics.get_word_declensions();
let (first, second, third): (String, String, String) = (
all_word_declensions.0.into(),
all_word_declensions.1.into(),
all_word_declensions.2.into(),
);
match self.get_values() {
(0, 1) | (2..=9, 1) => Some(first),
(0, 2..=4) | (2..=9, 2..=4) => Some(second),
(0..=9, 0) | (0, 5..=9) | (1, 0..=9) | (2..=9, 5..=9) => Some(third),
_ => None,
}
}
}

View File

@ -0,0 +1,43 @@
use core::convert::Into;
use telers::{
errors::session::ErrorKind,
methods::SendMessage,
types::{Message, ReplyParameters},
Bot,
};
#[derive(Clone)]
pub struct MessageSender {
chat_id: i64,
send_message: SendMessage,
}
impl MessageSender {
pub fn new(group_id: i64) -> Self {
Self {
chat_id: group_id,
send_message: SendMessage::new(group_id, ""),
}
}
pub fn group_id(mut self, val: i64) -> Self {
self.chat_id = val;
self.send_message = self.send_message.clone().chat_id(val);
self
}
pub fn text(mut self, val: impl Into<String>) -> Self {
self.send_message = self.send_message.clone().text(val);
self
}
pub fn disable_notification(mut self, val: bool) -> Self {
self.send_message = self.send_message.clone().disable_notification(val);
self
}
pub fn reply_to(mut self, msg_id: i64) -> Self {
let reply = ReplyParameters::new(msg_id).chat_id(self.chat_id);
self.send_message = self.send_message.clone().reply_parameters(reply);
self
}
pub async fn send(self, bot: &Bot) -> Result<Message, ErrorKind> {
bot.send(self.send_message).await
}
}

3
src/types/structs/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod bot_entity;
pub mod countable_time;
pub mod message_sender;

View File

@ -0,0 +1,12 @@
#[allow(dead_code)]
pub trait ICountable<T, M> {
fn new() -> Self;
fn from_value(value: i64) -> Self;
fn set_values(&self, value: i64) -> Self;
fn get_values(&self) -> T;
fn get_postfix(&self, metrics: M) -> Option<String>;
}

1
src/types/traits/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod countable_interface;

1
src/utils/general/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod unrestrict_date;

View File

@ -0,0 +1,5 @@
use chrono::{Duration, Local, NaiveDateTime};
pub fn unrestrict_date(days: i64) -> NaiveDateTime {
Local::now().naive_utc() + Duration::days(days)
}

2
src/utils/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod general;
pub mod telegram;

View File

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

View File

@ -0,0 +1,5 @@
use telers::{errors::session::ErrorKind, methods::BanChatMember, Bot};
pub async fn ban_chat_member(bot: &Bot, chat_id: i64, user_id: i64) -> Result<bool, ErrorKind> {
bot.send(BanChatMember::new(chat_id, user_id)).await
}

View File

@ -0,0 +1,20 @@
use telers::{errors::session::ErrorKind, methods::promote_chat_member::PromoteChatMember, Bot};
pub async fn demote_user(bot: &Bot, user_id: i64, chat_id: i64) -> Result<bool, ErrorKind> {
bot.send(
PromoteChatMember::new(chat_id, user_id)
.can_manage_topics(false)
.can_pin_messages(false)
.can_invite_users(false)
.can_change_info(false)
.can_promote_members(false)
.can_restrict_members(false)
.can_manage_voice_chats(false)
.can_delete_messages(false)
.can_edit_messages(false)
.can_post_messages(false)
.can_manage_chat(false)
.is_anonymous(false),
)
.await
}

View File

@ -0,0 +1,6 @@
use telers::{errors::SessionErrorKind, methods::get_chat_administrators, types::ChatMember, Bot};
pub async fn get_all_admins(bot: &Bot, chat_id: i64) -> Result<Vec<ChatMember>, SessionErrorKind> {
bot.send(get_chat_administrators::GetChatAdministrators::new(chat_id))
.await
}

View File

@ -0,0 +1,6 @@
pub mod admin_check;
pub mod ban_member;
pub mod demote;
pub mod get_all_admins;
pub mod restrict;
pub mod try_do_action;

View File

@ -0,0 +1,32 @@
use chrono::NaiveDateTime;
use telers::{errors::session::ErrorKind, methods::*, types::ChatPermissions, Bot};
pub async fn restrict(
bot: &Bot,
user_id: i64,
duration: NaiveDateTime,
chat_id: i64,
) -> Result<bool, ErrorKind> {
bot.send(
restrict_chat_member::RestrictChatMember::new(
chat_id,
user_id,
ChatPermissions::new()
.can_send_photos(false)
.can_send_polls(false)
.can_send_audios(false)
.can_send_videos(false)
.can_change_info(false)
.can_invite_users(false)
.can_send_other_messages(false)
.can_pin_messages(false)
.can_send_messages(false)
.can_send_voice_notes(false)
.can_send_video_notes(false)
.can_send_documents(false)
.can_manage_topics(false),
)
.until_date(duration.and_utc().timestamp()),
)
.await
}

View File

@ -0,0 +1,41 @@
use telers::{errors::SessionErrorKind as ErrorKind, event::EventReturn, Bot};
use crate::types::structs::message_sender::MessageSender;
use std::future::Future;
use super::demote::demote_user;
const DEMOTE_FAILURE_MESSAGE: &str = "\
Нельзя выдать ограничение пользователю, т.к. невозможно демотнуть \
участника посредством бота, если ему выдан админ при помощи других \
админов или владельца чата.";
pub async fn try_restrict<F, R>(
future_callback: F,
demote_args: (&Bot, i64, i64),
failure_sender: MessageSender,
) -> Result<(), EventReturn>
where
R: Future<Output = Result<bool, ErrorKind>>,
F: Copy + Fn() -> R,
{
let (bot, user_id, chat_id): (&Bot, i64, i64) = demote_args;
if future_callback().await.is_err() {
if demote_user(bot, user_id, chat_id).await.is_err() {
MessageSender::new(chat_id)
.text(DEMOTE_FAILURE_MESSAGE)
.send(bot)
.await
.unwrap();
return Err(EventReturn::Cancel);
}
if future_callback().await.is_err() {
failure_sender.send(bot).await.unwrap();
return Err(EventReturn::Cancel);
}
}
Ok(())
}