feat: Notification when users are banned
When `ban_alert` is set to true, the Telegram bot will send a notification after banning users Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
3e6c4dece8
commit
6070ca035c
5 changed files with 65 additions and 22 deletions
|
@ -71,11 +71,14 @@ pub struct Forgejo {
|
|||
#[derive(Deserialize)]
|
||||
pub struct Telegram {
|
||||
/// Telegram bot token
|
||||
pub token: String,
|
||||
pub token: String,
|
||||
/// Chat to send the alert in
|
||||
pub chat: ChatId,
|
||||
pub chat: ChatId,
|
||||
/// Send an alert when ban a user
|
||||
#[serde(default)]
|
||||
pub ban_alert: bool,
|
||||
/// Bot language
|
||||
pub lang: Lang,
|
||||
pub lang: Lang,
|
||||
}
|
||||
|
||||
/// The expression
|
||||
|
|
|
@ -38,6 +38,9 @@ async fn try_main() -> error::GuardResult<()> {
|
|||
// Suspicious users are sent and received in this channel, users who meet the
|
||||
// `alert` expressions
|
||||
let (sus_sender, sus_receiver) = sync::mpsc::channel::<forgejo_api::ForgejoUser>(100);
|
||||
// Banned users (already banned) are sent and received in this channel, this
|
||||
// to alert the admins on Telegram if `ban_alert` is set to true
|
||||
let (ban_sender, ban_receiver) = sync::mpsc::channel::<forgejo_api::ForgejoUser>(100);
|
||||
|
||||
tracing::info!("The instance: {}", config.forgejo.instance);
|
||||
tracing::info!("Dry run: {}", config.dry_run);
|
||||
|
@ -48,13 +51,15 @@ async fn try_main() -> error::GuardResult<()> {
|
|||
tokio::spawn(users_fetcher::users_fetcher(
|
||||
Arc::clone(&config),
|
||||
cancellation_token.clone(),
|
||||
sus_sender.clone(),
|
||||
sus_sender,
|
||||
ban_sender,
|
||||
));
|
||||
|
||||
tokio::spawn(telegram_bot::start_bot(
|
||||
Arc::clone(&config),
|
||||
cancellation_token.clone(),
|
||||
sus_receiver,
|
||||
ban_receiver,
|
||||
));
|
||||
|
||||
tokio::select! {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
mod callback_handler;
|
||||
mod message_handler;
|
||||
mod sus_handler;
|
||||
mod users_handler;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -55,6 +55,7 @@ pub async fn start_bot(
|
|||
config: Arc<Config>,
|
||||
cancellation_token: CancellationToken,
|
||||
sus_receiver: Receiver<ForgejoUser>,
|
||||
ban_receiver: Receiver<ForgejoUser>,
|
||||
) {
|
||||
tracing::info!("Starting the telegram bot");
|
||||
|
||||
|
@ -66,11 +67,12 @@ pub async fn start_bot(
|
|||
)
|
||||
.branch(Update::filter_callback_query().endpoint(callback_handler));
|
||||
|
||||
tokio::spawn(sus_handler::sus_users_handler(
|
||||
tokio::spawn(users_handler::users_handler(
|
||||
bot.clone(),
|
||||
Arc::clone(&config),
|
||||
cancellation_token,
|
||||
sus_receiver,
|
||||
ban_receiver,
|
||||
));
|
||||
|
||||
Dispatcher::builder(bot, handler)
|
||||
|
|
|
@ -48,6 +48,20 @@ fn not_found_if_empty(text: &str) -> Cow<'_, str> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generate a user details message
|
||||
fn user_details(msg: &str, user: &ForgejoUser) -> String {
|
||||
t!(
|
||||
msg,
|
||||
user_id = user.id,
|
||||
username = user.username,
|
||||
full_name = not_found_if_empty(&user.full_name),
|
||||
bio = not_found_if_empty(&user.biography),
|
||||
website = not_found_if_empty(&user.website),
|
||||
profile = user.html_url,
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Send a suspicious user alert to the admins
|
||||
pub async fn send_sus_alert(
|
||||
bot: &Bot,
|
||||
|
@ -56,34 +70,45 @@ pub async fn send_sus_alert(
|
|||
) -> ResponseResult<()> {
|
||||
let keyboard = make_sus_inline_keyboard(&sus_user);
|
||||
|
||||
let caption = user_details("messages.sus_alert", &sus_user);
|
||||
bot.send_photo(config.telegram.chat, InputFile::url(sus_user.avatar_url))
|
||||
.caption(t!(
|
||||
"messages.sus_alert",
|
||||
user_id = sus_user.id,
|
||||
username = sus_user.username,
|
||||
full_name = not_found_if_empty(&sus_user.full_name),
|
||||
bio = not_found_if_empty(&sus_user.biography),
|
||||
website = not_found_if_empty(&sus_user.website),
|
||||
profile = sus_user.html_url,
|
||||
))
|
||||
.caption(caption)
|
||||
.reply_markup(keyboard)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle the suspicious users
|
||||
pub async fn sus_users_handler(
|
||||
/// Send a ban notification to the admins chat
|
||||
pub async fn send_ban_notify(
|
||||
bot: &Bot,
|
||||
sus_user: ForgejoUser,
|
||||
config: &Config,
|
||||
) -> ResponseResult<()> {
|
||||
let caption = user_details("messages.ban_notify", &sus_user);
|
||||
bot.send_photo(config.telegram.chat, InputFile::url(sus_user.avatar_url))
|
||||
.caption(caption)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle the suspicious and banned users
|
||||
pub async fn users_handler(
|
||||
bot: Bot,
|
||||
config: Arc<Config>,
|
||||
cancellation_token: CancellationToken,
|
||||
mut sus_receiver: Receiver<ForgejoUser>,
|
||||
mut ban_receiver: Receiver<ForgejoUser>,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(sus_user) = sus_receiver.recv() => {
|
||||
send_sus_alert(&bot, sus_user, &config).await.ok();
|
||||
}
|
||||
Some(banned_user) = ban_receiver.recv() => {
|
||||
send_ban_notify(&bot, banned_user, &config).await.ok();
|
||||
}
|
||||
_ = cancellation_token.cancelled() => {
|
||||
tracing::info!("sus users handler has been stopped successfully.");
|
||||
break;
|
|
@ -59,10 +59,13 @@ async fn check_new_user(
|
|||
request_client: &reqwest::Client,
|
||||
config: &Config,
|
||||
sus_sender: &Sender<ForgejoUser>,
|
||||
ban_sender: &Sender<ForgejoUser>,
|
||||
) {
|
||||
if let Some(re) = config.expressions.ban.is_match(&user) {
|
||||
tracing::info!("@{} has been banned because `{re}`", user.username);
|
||||
if config.dry_run {
|
||||
// If it's a dry run, we don't need to ban the user
|
||||
ban_sender.send(user).await.ok();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -75,10 +78,12 @@ async fn check_new_user(
|
|||
.await
|
||||
{
|
||||
tracing::error!("Error while banning a user: {err}");
|
||||
} else {
|
||||
ban_sender.send(user).await.ok();
|
||||
}
|
||||
} else if let Some(re) = config.expressions.sus.is_match(&user) {
|
||||
tracing::info!("@{} has been suspected because `{re}`", user.username);
|
||||
let _ = sus_sender.send(user).await;
|
||||
sus_sender.send(user).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,9 +91,10 @@ async fn check_new_user(
|
|||
/// banned users
|
||||
async fn check_new_users(
|
||||
last_user_id: Arc<AtomicUsize>,
|
||||
sus_sender: Sender<ForgejoUser>,
|
||||
request_client: Arc<reqwest::Client>,
|
||||
config: Arc<Config>,
|
||||
sus_sender: Sender<ForgejoUser>,
|
||||
ban_sender: Sender<ForgejoUser>,
|
||||
) {
|
||||
match get_new_users(
|
||||
&request_client,
|
||||
|
@ -108,7 +114,7 @@ async fn check_new_users(
|
|||
}
|
||||
|
||||
for user in new_users {
|
||||
check_new_user(user, &request_client, &config, &sus_sender).await;
|
||||
check_new_user(user, &request_client, &config, &sus_sender, &ban_sender).await;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -123,6 +129,7 @@ pub async fn users_fetcher(
|
|||
config: Arc<Config>,
|
||||
cancellation_token: CancellationToken,
|
||||
sus_sender: Sender<ForgejoUser>,
|
||||
ban_sender: Sender<ForgejoUser>,
|
||||
) {
|
||||
let last_user_id = Arc::new(AtomicUsize::new(0));
|
||||
let request_client = Arc::new(reqwest::Client::new());
|
||||
|
@ -130,12 +137,13 @@ pub async fn users_fetcher(
|
|||
tracing::info!("Starting users fetcher");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(Duration::from_secs(120)) => {
|
||||
_ = tokio::time::sleep(Duration::from_secs(20)) => {
|
||||
tokio::spawn(check_new_users(
|
||||
Arc::clone(&last_user_id),
|
||||
sus_sender.clone(),
|
||||
Arc::clone(&request_client),
|
||||
Arc::clone(&config),
|
||||
sus_sender.clone(),
|
||||
ban_sender.clone(),
|
||||
));
|
||||
}
|
||||
_ = cancellation_token.cancelled() => {
|
||||
|
|
Loading…
Reference in a new issue