implement download permission request system for chats

This commit is contained in:
mykola2312 2024-03-04 00:59:24 +02:00
parent c00d1686a5
commit d0fd88119c
4 changed files with 230 additions and 2 deletions

View file

@ -17,4 +17,12 @@ request_not_found: "Request not found"
request_approved: "Request has been approved. The user should now be able to download" 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" your_request_approved: "Congrats! Your request has been approved. Now you should be able to download"
request_declined: "Request has been declined (deleted)." request_declined: "Request has been declined (deleted)."
your_request_declined: "huge L for ya - ur request has been deleted and fuck you" 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)"

View file

@ -3,6 +3,7 @@ pub mod dl;
pub mod notify; pub mod notify;
pub mod op; pub mod op;
pub mod request; pub mod request;
pub mod request_chat;
pub mod sanitize; pub mod sanitize;
pub mod start; pub mod start;
pub mod types; pub mod types;

View file

@ -17,6 +17,7 @@ use crate::db::DbPool;
use super::dl::cmd_download; use super::dl::cmd_download;
use super::op::cmd_op; use super::op::cmd_op;
use super::request::{cmd_listrequests, cmd_request, cmd_approve, cmd_decline}; 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}; use super::start::{cmd_start, handle_my_chat_member};
fn parse_env<T>(name: &str) -> T fn parse_env<T>(name: &str) -> T
@ -66,7 +67,11 @@ fn schema() -> UpdateHandler<HandlerErr> {
.branch(case![Command::Request(text)].endpoint(cmd_request)) .branch(case![Command::Request(text)].endpoint(cmd_request))
.branch(case![Command::ListRequests].endpoint(cmd_listrequests)) .branch(case![Command::ListRequests].endpoint(cmd_listrequests))
.branch(case![Command::Approve(text)].endpoint(cmd_approve)) .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 message_handler = Update::filter_message().branch(command_handler);
let raw_message_handler = Update::filter_message().branch(dptree::endpoint(handle_message)); let raw_message_handler = Update::filter_message().branch(dptree::endpoint(handle_message));
@ -116,6 +121,7 @@ enum Command {
Download(String), Download(String),
#[command(alias = "op")] #[command(alias = "op")]
OP, OP,
#[command(alias = "request")] #[command(alias = "request")]
Request(String), Request(String),
#[command(alias = "listrequests")] #[command(alias = "listrequests")]
@ -124,6 +130,15 @@ enum Command {
Approve(String), Approve(String),
#[command(alias = "decline")] #[command(alias = "decline")]
Decline(String), 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 { async fn cmd_test(bot: Bot, msg: Message, _db: DbPool) -> HandlerResult {

204
src/bot/request_chat.rs Normal file
View file

@ -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<RequestChatWithChat> = 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<RequestChatWithChat, sqlx::Error> = 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<RequestChatWithChat, sqlx::Error> = 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(())
}