Compare commits
No commits in common. "5344d742f6716c8c49a32a83183cadbea7adb893" and "c3972b356642c3977b4a2477e4a5f1acd3db868f" have entirely different histories.
5344d742f6
...
c3972b3566
9 changed files with 23 additions and 97 deletions
|
@ -55,18 +55,12 @@ We use `TOML` format for configuration, the default configuration file is `/app/
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
In our configuration file you can have the following sections and the global sections:
|
In our configuration file, we have three main sections:
|
||||||
|
|
||||||
- `forgejo`: Forgejo instance configuration
|
- `forgejo`: Forgejo instance configuration
|
||||||
- `expressions`: Regular expressions to match against
|
- `expressions`: Regular expressions to match against
|
||||||
- `telegram`: Telegram bot configuration
|
- `telegram`: Telegram bot configuration
|
||||||
|
|
||||||
#### Global sections
|
|
||||||
|
|
||||||
The global section is the one that doesn't have a name, and it's in the top of the configuration file, with the following fields:
|
|
||||||
|
|
||||||
- `dry_run`: If set to `true`, the guardian will not ban the users, but will only alert the admins (default: `false`)
|
|
||||||
|
|
||||||
#### `forgejo`
|
#### `forgejo`
|
||||||
|
|
||||||
Forgejo configuration section, with the following fields:
|
Forgejo configuration section, with the following fields:
|
||||||
|
@ -110,7 +104,6 @@ Telegram bot configuration section, with the following fields:
|
||||||
|
|
||||||
- `token`: Telegram bot token
|
- `token`: Telegram bot token
|
||||||
- `chat`: Chat ID to send the alerts to (Can be a group or a channel or a user)
|
- `chat`: Chat ID to send the alerts to (Can be a group or a channel or a user)
|
||||||
- `ban_alert`: Send a notification when a user is banned (default: `false`)
|
|
||||||
- `lang`: Language to use for the alerts (Currently only `ar-sa`, `en-us` and `ru-ru` are supported)
|
- `lang`: Language to use for the alerts (Currently only `ar-sa`, `en-us` and `ru-ru` are supported)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
|
|
@ -10,14 +10,6 @@ sus_alert = """تم اكتشاف مستخدم مشبوه! 🚨
|
||||||
|
|
||||||
هل تريد حظر هذا المستخدم؟
|
هل تريد حظر هذا المستخدم؟
|
||||||
"""
|
"""
|
||||||
ban_notify = """تم حظر مستخدم ⛔
|
|
||||||
• معرف المستخدم: %{user_id}
|
|
||||||
• اسم المستخدم: %{username}
|
|
||||||
• الاسم الكامل: %{full_name}
|
|
||||||
• النبذة: %{bio}
|
|
||||||
• الموقع: %{website}
|
|
||||||
• الملف التعريفي: %{profile}
|
|
||||||
"""
|
|
||||||
ban_success = "تم حظر المستخدم بنجاح ⛔"
|
ban_success = "تم حظر المستخدم بنجاح ⛔"
|
||||||
ban_failed = "فشل حظر المستخدم! ⚠️"
|
ban_failed = "فشل حظر المستخدم! ⚠️"
|
||||||
ban_denied = "تم تجاهل المستخدم ⚠️"
|
ban_denied = "تم تجاهل المستخدم ⚠️"
|
||||||
|
|
|
@ -10,14 +10,6 @@ sus_alert = """Suspicious user detected! 🚨
|
||||||
|
|
||||||
Do you want to ban this user?
|
Do you want to ban this user?
|
||||||
"""
|
"""
|
||||||
ban_notify = """User has been banned ⛔
|
|
||||||
• User ID: %{user_id}
|
|
||||||
• Username: %{username}
|
|
||||||
• Full name: %{full_name}
|
|
||||||
• Bio: %{bio}
|
|
||||||
• Website: %{website}
|
|
||||||
• Profile: %{profile}
|
|
||||||
"""
|
|
||||||
ban_success = "User has been banned successfully ⛔"
|
ban_success = "User has been banned successfully ⛔"
|
||||||
ban_failed = "Failed to ban the user! ⚠️"
|
ban_failed = "Failed to ban the user! ⚠️"
|
||||||
ban_denied = "User has been ignored ⚠️"
|
ban_denied = "User has been ignored ⚠️"
|
||||||
|
|
|
@ -10,14 +10,6 @@ sus_alert = """Обнаружен подозрительный пользова
|
||||||
|
|
||||||
Хотите забанить этого пользователя?
|
Хотите забанить этого пользователя?
|
||||||
"""
|
"""
|
||||||
ban_notify = """Пользователь заблокирован ⛔
|
|
||||||
• ID пользователя: %{user_id}
|
|
||||||
• Юзернейм: %{username}
|
|
||||||
• Полное имя: %{full_name}
|
|
||||||
• Био: %{bio}
|
|
||||||
• Вебсайт: %{website}
|
|
||||||
• Профиль: %{profile}
|
|
||||||
"""
|
|
||||||
ban_success = "Пользователь успешно забанен ⛔"
|
ban_success = "Пользователь успешно забанен ⛔"
|
||||||
ban_failed = "Не удалось забанить пользователя! ⚠️"
|
ban_failed = "Не удалось забанить пользователя! ⚠️"
|
||||||
ban_denied = "Пользователь был проигнорирован ⚠️"
|
ban_denied = "Пользователь был проигнорирован ⚠️"
|
||||||
|
|
|
@ -71,14 +71,11 @@ pub struct Forgejo {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Telegram {
|
pub struct Telegram {
|
||||||
/// Telegram bot token
|
/// Telegram bot token
|
||||||
pub token: String,
|
pub token: String,
|
||||||
/// Chat to send the alert in
|
/// 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
|
/// Bot language
|
||||||
pub lang: Lang,
|
pub lang: Lang,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The expression
|
/// The expression
|
||||||
|
|
|
@ -38,9 +38,6 @@ async fn try_main() -> error::GuardResult<()> {
|
||||||
// Suspicious users are sent and received in this channel, users who meet the
|
// Suspicious users are sent and received in this channel, users who meet the
|
||||||
// `alert` expressions
|
// `alert` expressions
|
||||||
let (sus_sender, sus_receiver) = sync::mpsc::channel::<forgejo_api::ForgejoUser>(100);
|
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!("The instance: {}", config.forgejo.instance);
|
||||||
tracing::info!("Dry run: {}", config.dry_run);
|
tracing::info!("Dry run: {}", config.dry_run);
|
||||||
|
@ -51,15 +48,13 @@ async fn try_main() -> error::GuardResult<()> {
|
||||||
tokio::spawn(users_fetcher::users_fetcher(
|
tokio::spawn(users_fetcher::users_fetcher(
|
||||||
Arc::clone(&config),
|
Arc::clone(&config),
|
||||||
cancellation_token.clone(),
|
cancellation_token.clone(),
|
||||||
sus_sender,
|
sus_sender.clone(),
|
||||||
ban_sender,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
tokio::spawn(telegram_bot::start_bot(
|
tokio::spawn(telegram_bot::start_bot(
|
||||||
Arc::clone(&config),
|
Arc::clone(&config),
|
||||||
cancellation_token.clone(),
|
cancellation_token.clone(),
|
||||||
sus_receiver,
|
sus_receiver,
|
||||||
ban_receiver,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
mod callback_handler;
|
mod callback_handler;
|
||||||
mod message_handler;
|
mod message_handler;
|
||||||
mod users_handler;
|
mod sus_handler;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -55,7 +55,6 @@ pub async fn start_bot(
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
sus_receiver: Receiver<ForgejoUser>,
|
sus_receiver: Receiver<ForgejoUser>,
|
||||||
ban_receiver: Receiver<ForgejoUser>,
|
|
||||||
) {
|
) {
|
||||||
tracing::info!("Starting the telegram bot");
|
tracing::info!("Starting the telegram bot");
|
||||||
|
|
||||||
|
@ -67,12 +66,11 @@ pub async fn start_bot(
|
||||||
)
|
)
|
||||||
.branch(Update::filter_callback_query().endpoint(callback_handler));
|
.branch(Update::filter_callback_query().endpoint(callback_handler));
|
||||||
|
|
||||||
tokio::spawn(users_handler::users_handler(
|
tokio::spawn(sus_handler::sus_users_handler(
|
||||||
bot.clone(),
|
bot.clone(),
|
||||||
Arc::clone(&config),
|
Arc::clone(&config),
|
||||||
cancellation_token,
|
cancellation_token,
|
||||||
sus_receiver,
|
sus_receiver,
|
||||||
ban_receiver,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
Dispatcher::builder(bot, handler)
|
Dispatcher::builder(bot, handler)
|
||||||
|
|
|
@ -48,20 +48,6 @@ 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
|
/// Send a suspicious user alert to the admins
|
||||||
pub async fn send_sus_alert(
|
pub async fn send_sus_alert(
|
||||||
bot: &Bot,
|
bot: &Bot,
|
||||||
|
@ -70,45 +56,34 @@ pub async fn send_sus_alert(
|
||||||
) -> ResponseResult<()> {
|
) -> ResponseResult<()> {
|
||||||
let keyboard = make_sus_inline_keyboard(&sus_user);
|
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))
|
bot.send_photo(config.telegram.chat, InputFile::url(sus_user.avatar_url))
|
||||||
.caption(caption)
|
.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,
|
||||||
|
))
|
||||||
.reply_markup(keyboard)
|
.reply_markup(keyboard)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a ban notification to the admins chat
|
/// Handle the suspicious users
|
||||||
pub async fn send_ban_notify(
|
pub async fn sus_users_handler(
|
||||||
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,
|
bot: Bot,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
mut sus_receiver: Receiver<ForgejoUser>,
|
mut sus_receiver: Receiver<ForgejoUser>,
|
||||||
mut ban_receiver: Receiver<ForgejoUser>,
|
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Some(sus_user) = sus_receiver.recv() => {
|
Some(sus_user) = sus_receiver.recv() => {
|
||||||
send_sus_alert(&bot, sus_user, &config).await.ok();
|
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() => {
|
_ = cancellation_token.cancelled() => {
|
||||||
tracing::info!("sus users handler has been stopped successfully.");
|
tracing::info!("sus users handler has been stopped successfully.");
|
||||||
break;
|
break;
|
|
@ -59,13 +59,10 @@ async fn check_new_user(
|
||||||
request_client: &reqwest::Client,
|
request_client: &reqwest::Client,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
sus_sender: &Sender<ForgejoUser>,
|
sus_sender: &Sender<ForgejoUser>,
|
||||||
ban_sender: &Sender<ForgejoUser>,
|
|
||||||
) {
|
) {
|
||||||
if let Some(re) = config.expressions.ban.is_match(&user) {
|
if let Some(re) = config.expressions.ban.is_match(&user) {
|
||||||
tracing::info!("@{} has been banned because `{re}`", user.username);
|
tracing::info!("@{} has been banned because `{re}`", user.username);
|
||||||
if config.dry_run {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,12 +75,10 @@ async fn check_new_user(
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::error!("Error while banning a user: {err}");
|
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) {
|
} else if let Some(re) = config.expressions.sus.is_match(&user) {
|
||||||
tracing::info!("@{} has been suspected because `{re}`", user.username);
|
tracing::info!("@{} has been suspected because `{re}`", user.username);
|
||||||
sus_sender.send(user).await.ok();
|
let _ = sus_sender.send(user).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +86,9 @@ async fn check_new_user(
|
||||||
/// banned users
|
/// banned users
|
||||||
async fn check_new_users(
|
async fn check_new_users(
|
||||||
last_user_id: Arc<AtomicUsize>,
|
last_user_id: Arc<AtomicUsize>,
|
||||||
|
sus_sender: Sender<ForgejoUser>,
|
||||||
request_client: Arc<reqwest::Client>,
|
request_client: Arc<reqwest::Client>,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
sus_sender: Sender<ForgejoUser>,
|
|
||||||
ban_sender: Sender<ForgejoUser>,
|
|
||||||
) {
|
) {
|
||||||
match get_new_users(
|
match get_new_users(
|
||||||
&request_client,
|
&request_client,
|
||||||
|
@ -114,7 +108,7 @@ async fn check_new_users(
|
||||||
}
|
}
|
||||||
|
|
||||||
for user in new_users {
|
for user in new_users {
|
||||||
check_new_user(user, &request_client, &config, &sus_sender, &ban_sender).await;
|
check_new_user(user, &request_client, &config, &sus_sender).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -129,7 +123,6 @@ pub async fn users_fetcher(
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
sus_sender: Sender<ForgejoUser>,
|
sus_sender: Sender<ForgejoUser>,
|
||||||
ban_sender: Sender<ForgejoUser>,
|
|
||||||
) {
|
) {
|
||||||
let last_user_id = Arc::new(AtomicUsize::new(0));
|
let last_user_id = Arc::new(AtomicUsize::new(0));
|
||||||
let request_client = Arc::new(reqwest::Client::new());
|
let request_client = Arc::new(reqwest::Client::new());
|
||||||
|
@ -137,13 +130,12 @@ pub async fn users_fetcher(
|
||||||
tracing::info!("Starting users fetcher");
|
tracing::info!("Starting users fetcher");
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = tokio::time::sleep(Duration::from_secs(20)) => {
|
_ = tokio::time::sleep(Duration::from_secs(120)) => {
|
||||||
tokio::spawn(check_new_users(
|
tokio::spawn(check_new_users(
|
||||||
Arc::clone(&last_user_id),
|
Arc::clone(&last_user_id),
|
||||||
|
sus_sender.clone(),
|
||||||
Arc::clone(&request_client),
|
Arc::clone(&request_client),
|
||||||
Arc::clone(&config),
|
Arc::clone(&config),
|
||||||
sus_sender.clone(),
|
|
||||||
ban_sender.clone(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ = cancellation_token.cancelled() => {
|
_ = cancellation_token.cancelled() => {
|
||||||
|
|
Loading…
Reference in a new issue