diff --git a/locales/en.yml b/locales/en.yml index 05a3cea..e759395 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -17,4 +17,12 @@ request_not_found: "Request not found" request_approved: "Request has been approved. The user should now be able to download" your_request_approved: "Congrats! Your request has been approved. Now you should be able to download" request_declined: "Request has been declined (deleted)." -your_request_declined: "huge L for ya - ur request has been deleted and fuck you" \ No newline at end of file +your_request_declined: "huge L for ya - ur request has been deleted and fuck you" +chat_already_can_download: "This chat already has permission for all users to download. Enjoy!" +chat_already_has_requested: "Downloading permission request for this chat is pending. Wait patiently or this shithole will be nuked" +admin_notify_chat_request: "Chat %{chat} awaits request approval" +chat_request_added: "Downloading permission request for this chat has been added. Admins have been notified. Wait for approval or decline, bot will reply when that happens" +chat_request_list_header: "Current chat requests for downloading:\n" +chat_request_not_found: "Chat request not found" +chat_request_approved: "Chat request has been approved. Now everyone in this chat can download" +chat_request_declined: "Very bad news! This chat will be drone-striked tomorrow (chat request declined)" \ No newline at end of file diff --git a/src/bot.rs b/src/bot.rs index 30a011a..8ba1d95 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -3,6 +3,7 @@ pub mod dl; pub mod notify; pub mod op; pub mod request; +pub mod request_chat; pub mod sanitize; pub mod start; pub mod types; diff --git a/src/bot/bot.rs b/src/bot/bot.rs index f2d50f1..a99cd97 100644 --- a/src/bot/bot.rs +++ b/src/bot/bot.rs @@ -17,6 +17,7 @@ use crate::db::DbPool; use super::dl::cmd_download; use super::op::cmd_op; use super::request::{cmd_listrequests, cmd_request, cmd_approve, cmd_decline}; +use super::request_chat::{cmd_listrequests_chat, cmd_request_chat, cmd_approve_chat, cmd_decline_chat}; use super::start::{cmd_start, handle_my_chat_member}; fn parse_env(name: &str) -> T @@ -66,7 +67,11 @@ fn schema() -> UpdateHandler { .branch(case![Command::Request(text)].endpoint(cmd_request)) .branch(case![Command::ListRequests].endpoint(cmd_listrequests)) .branch(case![Command::Approve(text)].endpoint(cmd_approve)) - .branch(case![Command::Decline(text)].endpoint(cmd_decline)); + .branch(case![Command::Decline(text)].endpoint(cmd_decline)) + .branch(case![Command::RequestChat(text)].endpoint(cmd_request_chat)) + .branch(case![Command::ListRequestsChat].endpoint(cmd_listrequests_chat)) + .branch(case![Command::ApproveChat(text)].endpoint(cmd_approve_chat)) + .branch(case![Command::DeclineChat(text)].endpoint(cmd_decline_chat)); let message_handler = Update::filter_message().branch(command_handler); let raw_message_handler = Update::filter_message().branch(dptree::endpoint(handle_message)); @@ -116,6 +121,7 @@ enum Command { Download(String), #[command(alias = "op")] OP, + #[command(alias = "request")] Request(String), #[command(alias = "listrequests")] @@ -124,6 +130,15 @@ enum Command { Approve(String), #[command(alias = "decline")] Decline(String), + + #[command(alias = "request_chat")] + RequestChat(String), + #[command(alias = "listrequests_chat")] + ListRequestsChat, + #[command(alias = "approve_chat")] + ApproveChat(String), + #[command(alias = "decline_chat")] + DeclineChat(String), } async fn cmd_test(bot: Bot, msg: Message, _db: DbPool) -> HandlerResult { diff --git a/src/bot/request_chat.rs b/src/bot/request_chat.rs new file mode 100644 index 0000000..a33d5dd --- /dev/null +++ b/src/bot/request_chat.rs @@ -0,0 +1,204 @@ +use rust_i18n::t; +use sqlx::Row; +use teloxide::types::Recipient; +use teloxide::prelude::*; +use tracing::{event, Level}; + +use super::notify::notify_admins; +use super::types::HandlerResult; +use crate::db::user::find_or_create_user; +use crate::db::chat::find_or_create_chat; +use crate::db::{DbPool, Chat}; +use crate::{parse_integer, reply_i18n_and_return}; + +pub async fn cmd_request_chat(bot: Bot, msg: Message, text: String, db: DbPool) -> HandlerResult { + if text.len() < 16 { + reply_i18n_and_return!(bot, msg.chat.id, "request_text_is_too_short"); + } else if text.len() > 100 { + reply_i18n_and_return!(bot, msg.chat.id, "request_text_is_too_long"); + } + + if let Some(user) = msg.from() { + let user = find_or_create_user(&db, user).await?; + let chat = find_or_create_chat(&db, &msg.chat).await?; + + if chat.can_download == 1 { + reply_i18n_and_return!(bot, msg.chat.id, "chat_already_can_download"); + } + + let requests: i64 = sqlx::query("SELECT COUNT(1) FROM request_chat WHERE requested_for = $1;") + .bind(chat.id) + .fetch_one(&db) + .await? + .get(0); + if requests > 0 { + reply_i18n_and_return!(bot, msg.chat.id, "chat_already_has_requested"); + } + + // put the chat request + sqlx::query("INSERT INTO request_chat (requested_by,requested_for,message,is_approved) VALUES ($1,$2,$3,$4);") + .bind(user.id) + .bind(chat.id) + .bind(text) + .bind(0) + .execute(&db) + .await?; + event!(Level::INFO, "added chat request for {}", chat); + + // notify admins + notify_admins( + &bot, + &db, + t!("admin_notify_chat_request", chat = chat.to_string()).to_string(), + ) + .await?; + + bot.send_message(msg.chat.id, t!("chat_request_added")).await?; + } + + Ok(()) +} + +#[derive(sqlx::FromRow, Debug)] +struct RequestChatWithChat { + pub request_id: i64, + pub message: String, + #[sqlx(flatten)] + pub chat: Chat, +} + +pub async fn cmd_listrequests_chat(bot: Bot, msg: Message, db: DbPool) -> HandlerResult { + if let Some(user) = msg.from() { + let user = find_or_create_user(&db, user).await?; + if user.is_admin != 1 { + reply_i18n_and_return!(bot, msg.chat.id, "not_an_admin"); + } + + let requests: Vec = sqlx::query_as( + "SELECT request_chat.id AS request_id, request_chat.message, chat.* + FROM request_chat + INNER JOIN chat ON request_chat.requested_for = chat.id + WHERE request_chat.is_approved = 0;", + ) + .fetch_all(&db) + .await?; + + let mut list = String::new(); + list.push_str(t!("chat_request_list_header").to_string().as_str()); + for request in requests { + let fmt = format!( + "{}: {}: {}\n", + request.request_id, request.chat, request.message + ); + list.push_str(fmt.as_str()); + } + bot.send_message(msg.chat.id, list).await?; + } + + Ok(()) +} + +pub async fn cmd_approve_chat(bot: Bot, msg: Message, id: String, db: DbPool) -> HandlerResult { + let id: i64 = parse_integer!(bot, msg.chat.id, id); + + if let Some(user) = msg.from() { + let user = find_or_create_user(&db, user).await?; + if user.is_admin != 1 { + reply_i18n_and_return!(bot, msg.chat.id, "not_an_admin"); + } + + // get request + let res: Result = sqlx::query_as( + "SELECT request_chat.id AS request_id, request_chat.message, chat.* + FROM request_chat + INNER JOIN chat ON request_chat.requested_for = chat.id + WHERE request_chat.is_approved = 0 + LIMIT 1;", + ) + .bind(id) + .fetch_one(&db) + .await; + let request = match res { + Ok(request) => request, + Err(e) => match e { + sqlx::Error::RowNotFound => { + bot.send_message(msg.chat.id, t!("chat_request_not_found")) + .await?; + return Ok(()); + } + _ => return Err(Box::new(e)), + }, + }; + + // approve request + sqlx::query("UPDATE request_chat SET approved_by = $1, is_approved = 1 WHERE id = $2;") + .bind(user.id) + .bind(request.request_id) + .execute(&db) + .await?; + event!( + Level::INFO, + "approved chat request {} by {} for {}", + request.request_id, + user, + request.chat + ); + // notify target chat + bot.send_message(Recipient::Id(ChatId(request.chat.tg_id)), t!("chat_request_approved")).await?; + } + + Ok(()) +} + +pub async fn cmd_decline_chat(bot: Bot, msg: Message, id: String, db: DbPool) -> HandlerResult { + let id: i64 = parse_integer!(bot, msg.chat.id, id); + + if let Some(user) = msg.from() { + let user = find_or_create_user(&db, user).await?; + if user.is_admin != 1 { + reply_i18n_and_return!(bot, msg.chat.id, "not_an_admin"); + } + + // get request + let res: Result = sqlx::query_as( + "SELECT request_chat.id AS request_id, request_chat.message, chat.* + FROM request_chat + INNER JOIN chat ON request_chat.requested_for = chat.id + WHERE request_chat.is_approved = 0 + LIMIT 1;", + ) + .bind(id) + .fetch_one(&db) + .await; + let request = match res { + Ok(request) => request, + Err(e) => match e { + sqlx::Error::RowNotFound => { + bot.send_message(msg.chat.id, t!("chat_request_not_found")) + .await?; + return Ok(()); + } + _ => return Err(Box::new(e)), + }, + }; + + // decline request + sqlx::query("DELETE FROM request_chat WHERE id = $1;") + .bind(request.request_id) + .execute(&db).await?; + event!( + Level::INFO, + "declined request {} by {} for {}", + request.request_id, + user, + request.chat + ); + bot.send_message(msg.chat.id, t!("request_declined")) + .await?; + + // notify target chat + bot.send_message(Recipient::Id(ChatId(request.chat.tg_id)), t!("chat_request_declined")).await?; + } + + Ok(()) +} \ No newline at end of file