Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package forgejo-guardian for 
openSUSE:Factory checked in at 2025-01-29 16:18:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/forgejo-guardian (Old)
 and      /work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "forgejo-guardian"

Wed Jan 29 16:18:23 2025 rev:2 rq:1241173 version:0.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/forgejo-guardian/forgejo-guardian.changes        
2025-01-23 18:03:03.458301207 +0100
+++ 
/work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316/forgejo-guardian.changes  
    2025-01-29 16:19:02.665032992 +0100
@@ -1,0 +2,11 @@
+Tue Jan 28 20:15:49 UTC 2025 - Richard Rahl <rra...@opensuse.org>
+
+- update to 0.5.0:
+  * Ability to check user tokens and oauth2 apps
+  * The minimum value for inactive.req_limit changed to 4
+  * Ability to enter seconds in the interval without s suffix
+  * Make expressions.interval suffixably
+  * Guardian checks all instance users when expressions.only_new_users is false
+  * Prevent async deadlock when Telegram is disabled
+
+-------------------------------------------------------------------

Old:
----
  forgejo-guardian-0.4.1.obscpio

New:
----
  forgejo-guardian-0.5.0.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ forgejo-guardian.spec ++++++
--- /var/tmp/diff_new_pack.L9FkpA/_old  2025-01-29 16:19:03.897084084 +0100
+++ /var/tmp/diff_new_pack.L9FkpA/_new  2025-01-29 16:19:03.897084084 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           forgejo-guardian
-Version:        0.4.1
+Version:        0.5.0
 Release:        0
 Summary:        Simple Forgejo instance guardian
 License:        AGPL-3.0-or-later

++++++ _service ++++++
--- /var/tmp/diff_new_pack.L9FkpA/_old  2025-01-29 16:19:03.921085079 +0100
+++ /var/tmp/diff_new_pack.L9FkpA/_new  2025-01-29 16:19:03.925085245 +0100
@@ -3,7 +3,7 @@
   <service name="obs_scm" mode="manual">
     <param name="scm">git</param>
     <param name="url">https://git.4rs.nl/awiteb/forgejo-guardian</param>
-    <param name="revision">refs/tags/v0.4.1</param>
+    <param name="revision">refs/tags/v0.5.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
   </service>

++++++ forgejo-guardian-0.4.1.obscpio -> forgejo-guardian-0.5.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/CHANGELOG.md 
new/forgejo-guardian-0.5.0/CHANGELOG.md
--- old/forgejo-guardian-0.4.1/CHANGELOG.md     2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/CHANGELOG.md     2025-01-28 20:22:43.000000000 
+0100
@@ -6,6 +6,16 @@
 
 ## unreleased
 ### Added
+-  Ability to check user tokens and oauth2 apps 
([**#25**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/25)) 
([`1e90760`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/1e907609cd9fae24e58cbe9eab99cc4b88459cb3))
+    - **BC**:  The minimum value for `inactive.req_limit` changed to 4
+-  Ability to enter seconds in the interval without `s` suffix 
([**#28**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/28)) 
([`c9dfc6e`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/c9dfc6e57acdfbdbc2485d729ce24edcab292224))
+-  Make `expressions.interval` suffixably 
([**#29**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/29)) 
([`e32aca7`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/e32aca7164669532ca08ee356fc01aaf6185aa67))
+### Fixed
+-  Guardian checks all instance users when `expressions.only_new_users` is 
`false` ([**#27**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/27)) 
([`cf05a68`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/cf05a68c22f1eeffc3308856c7117ff6d82855da))
+-  Prevent async deadlock when Telegram is disabled 
([**#31**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/31)) 
([`2ee7849`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/2ee784916305d32705d6a667ce1979c47f67874f))
+
+## [0.4.1](https://git.4rs.nl/awiteb/forgejo-guardian/compare/v0.4.0..v0.4.1) 
- 2025-01-22
+### Added
 -  Add support for including and excluding users 
([**#22**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/22)) 
([`f07fdab`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/f07fdaba7e9a37b87848e6d0bbb6b639c84cfd95))
 ### Fixed
 -  Check for the user activities for more than last 365 days 
([`e41b9b3`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/e41b9b36f67ff465731a20c1baaca9a9e6440bd0))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/Cargo.lock 
new/forgejo-guardian-0.5.0/Cargo.lock
--- old/forgejo-guardian-0.4.1/Cargo.lock       2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/Cargo.lock       2025-01-28 20:22:43.000000000 
+0100
@@ -332,7 +332,7 @@
 
 [[package]]
 name = "forgejo-guardian"
-version = "0.4.1"
+version = "0.5.0"
 dependencies = [
  "chrono",
  "regex",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/Cargo.toml 
new/forgejo-guardian-0.5.0/Cargo.toml
--- old/forgejo-guardian-0.4.1/Cargo.toml       2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/Cargo.toml       2025-01-28 20:22:43.000000000 
+0100
@@ -1,7 +1,7 @@
 [package]
 name        = "forgejo-guardian"
 description = "Simple Forgejo instance guardian, banning users and alerting 
admins based on certain regular expressions"
-version     = "0.4.1"
+version     = "0.5.0"
 edition     = "2021"
 authors     = ["Awiteb <a...@4rs.nl>"]
 repository  = "https://git.4rs.nl/awiteb/forgejo-guardian";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/README.md 
new/forgejo-guardian-0.5.0/README.md
--- old/forgejo-guardian-0.4.1/README.md        2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/README.md        2025-01-28 20:22:43.000000000 
+0100
@@ -66,7 +66,7 @@
 ```yaml
 services:
     forgejo-guardian:
-        image: git.4rs.nl/awiteb/forgejo-guardian:0.4
+        image: git.4rs.nl/awiteb/forgejo-guardian:0.5
         volumes:
             - ./forgejo-guardian.toml:/app/forgejo-guardian.toml:ro
 ```
@@ -89,7 +89,7 @@
 #### Without building the image
 
 ```sh
-docker run --rm -d -v $PWD/forgejo-guardian.toml:/app/forgejo-guardian.toml:ro 
git.4rs.nl/awiteb/forgejo-guardian:0.4
+docker run --rm -d -v $PWD/forgejo-guardian.toml:/app/forgejo-guardian.toml:ro 
git.4rs.nl/awiteb/forgejo-guardian:0.5
 ```
 
 ## Installation
@@ -176,12 +176,20 @@
 
 Inactive users configuration section, with the following fields:
 
+> [!NOTE]
+> The field may start with a version, this version is the required Forgejo
+> version, so you can use this version or later
+
 -   `enabled`: Enable the cleanup of inactive users, inactive feature need 
`read:user` scope (default: `false`)
 -   `exclude`: List of usernames to exclude from the cleanup (default: `[]`)
 -   `source_id`: List of source IDs to only consider users from (default: `[]`)
 -   `source_id_exclude`: List of source IDs to exclude users from (default: 
`[]`)
+-   **v10.0.1** `check_tokens`: Check if the user has tokens, if true the user
+    will not be considered (default: `true`)
+-   **v10.0.1** `check_oauth2`: Check if the user has OAuth2 applications, if
+    true the user will not be considered (default: `true`)
 -   `days`: The number of days that a new user is given to become active. 
(default: `30`)
--   `req_limit`: Maximum number of requests to send to the Forgejo instance 
within each interval (default: `200`) (Minimum: `2`)
+-   `req_limit`: Maximum number of requests to send to the Forgejo instance 
within each interval (default: `200`) (Minimum: `4`)
 -   `req_interval`: Time interval to pause after reaching the `req_limit` 
(default: `10m`)
 -   `interval`: Time Interval to check for inactive users (default: `7d`)
 
@@ -214,8 +222,10 @@
 Expressions configuration section, with the following fields:
 
 -   `only_new_users`: If set to `true`, the guardian will only check the new 
users, and not the existing ones (default: `false`)
--   `interval`: Interval to check for new users in seconds (default: `300`)
+-   `interval`: Interval to check for new users in seconds (default: `300s`)
 -   `limit`: Limit of users to fetch in each interval (default: `100`)
+-   `req_limit`: Maximum number of requests to send to the Forgejo instance 
within each interval (default: `200`) (Minimum: `1`) *
+-   `req_interval`: Time interval to pause after reaching the `req_limit` 
(default: `10m`) *
 -   `ban_alert`: Send a notification when a user is banned (default: `false`)
 -   `ban_action`: The action to take when a user is banned, can be one of the 
following:
     -   `purge` (default): Forcibly delete user and any repositories, 
organizations, and
@@ -227,6 +237,15 @@
 -   `ban`: Regular expressions to match against to ban the user
 -   `sus`: Regular expressions to match against to alert the admins
 
+The `expressions.interval` and `expressions.req_interval` have the following 
suffixes:
+
+-   `s`: Seconds
+-   `m`: Minutes
+-   `h`: Hours
+-   `d`: Days
+
+*: Only for checking old users, if `only_new_users` is set to `true`, the 
guardian will not use these values.
+
 `ban` and `sus` are tables, and each one have the following fields:
 
 -   `enabled`: Enable the expressions (default: enabled if the section is 
present,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/docker/Dockerfile 
new/forgejo-guardian-0.5.0/docker/Dockerfile
--- old/forgejo-guardian-0.4.1/docker/Dockerfile        2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/docker/Dockerfile        2025-01-28 
20:22:43.000000000 +0100
@@ -3,7 +3,7 @@
 WORKDIR /app
 
 RUN apk add --no-cache curl
-RUN curl 
https://git.4rs.nl/awiteb/forgejo-guardian/releases/download/v0.4.1/forgejo-guardian-v0.4.1-x86_64-linux-musl
 --output forgejo-guardian
+RUN curl 
https://git.4rs.nl/awiteb/forgejo-guardian/releases/download/v0.5.0/forgejo-guardian-v0.5.0-x86_64-linux-musl
 --output forgejo-guardian
 RUN chmod +x forgejo-guardian
 
 ENTRYPOINT ["./forgejo-guardian"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/defaults.rs 
new/forgejo-guardian-0.5.0/src/config/defaults.rs
--- old/forgejo-guardian-0.4.1/src/config/defaults.rs   2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/config/defaults.rs   2025-01-28 
20:22:43.000000000 +0100
@@ -23,6 +23,14 @@
     pub const fn ban_action() -> BanAction {
         BanAction::Purge
     }
+
+    pub const fn req_limit() -> u32 {
+        200
+    }
+
+    pub const fn req_interval() -> u32 {
+        10 * 60
+    }
 }
 
 /// Default configuration for inactive section.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/deserializers.rs 
new/forgejo-guardian-0.5.0/src/config/deserializers.rs
--- old/forgejo-guardian-0.4.1/src/config/deserializers.rs      2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/config/deserializers.rs      2025-01-28 
20:22:43.000000000 +0100
@@ -121,8 +121,19 @@
 where
     D: de::Deserializer<'de>,
 {
-    let interval = String::deserialize(des)
-        .map_err(|_| de::Error::custom("Expected a suffixed interval, e.g. 1s, 
2m"))?;
+    let toml_value = Value::deserialize(des)?;
+
+    if let Value::Integer(interval) = toml_value {
+        return u32::try_from(interval)
+            .map_err(|_| de::Error::custom("It is a negative number, it must 
be positive"));
+    }
+
+    let Value::String(interval) = toml_value else {
+        return Err(de::Error::custom(
+            "Expected a suffixed interval, e.g. 1s, 2m",
+        ));
+    };
+
     if interval.chars().count() < 2 {
         return Err(de::Error::custom(format!(
             "Expected a suffixed interval, e.g. 1s, 2m. found {interval}"
@@ -142,10 +153,10 @@
     })?;
     let suffix = interval.chars().last().expect("the length more than 2");
     let interval = match suffix {
-        's' => number,
-        'm' => number * 60,
-        'h' => number * 60 * 60,
-        'd' => number * 24 * 60 * 60,
+        's' => Some(number),
+        'm' => number.checked_mul(60),
+        'h' => number.checked_mul(60 * 60),
+        'd' => number.checked_mul(24 * 60 * 60),
         _ => {
             return Err(de::Error::custom(format!(
                 "Unknown suffix `{suffix}`, expected s, m, h, d"
@@ -153,7 +164,9 @@
         }
     };
 
-    Ok(interval)
+    interval.ok_or_else(|| {
+        de::Error::custom("The interval is too large, the maximum value is 
49710 days.")
+    })
 }
 
 /// Deserialize the deserializer into `T` then check if the value is greater
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/mod.rs 
new/forgejo-guardian-0.5.0/src/config/mod.rs
--- old/forgejo-guardian-0.4.1/src/config/mod.rs        2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/config/mod.rs        2025-01-28 
20:22:43.000000000 +0100
@@ -48,12 +48,19 @@
     /// Source ID to exclude
     #[serde(default)]
     pub source_id_exclude: Vec<u32>,
+    /// Check if the user has tokens, if true the user will not be considered
+    #[serde(default = "defaults::bool_true")]
+    pub check_tokens:      bool,
+    /// Check if the user has OAuth2 applications, if true the user will not be
+    /// considered
+    #[serde(default = "defaults::bool_true")]
+    pub check_oauth2:      bool,
     /// Number of inactive days to consider
     #[serde(default = "defaults::inactive::days")]
     pub days:              u64,
     /// Number of requests to send
     #[serde(default = "defaults::inactive::req_limit")]
-    #[serde(deserialize_with = "deserializers::unsigned_minimum::<_, _, 2>")]
+    #[serde(deserialize_with = "deserializers::unsigned_minimum::<_, _, 4>")]
     pub req_limit:         u16,
     /// Time interval in seconds for the request limit
     #[serde(
@@ -171,11 +178,26 @@
     #[serde(default)]
     pub only_new_users: bool,
     /// Interval to check for new users in seconds
-    #[serde(default = "defaults::expressions::interval")]
+    #[serde(
+        default = "defaults::expressions::interval",
+        deserialize_with = "deserializers::suffix_interval"
+    )]
     pub interval:       u32,
     /// Limit of users to fetch in each interval
     #[serde(default = "defaults::expressions::limit")]
     pub limit:          u32,
+    /// Maximum number of requests to send
+    #[serde(
+        default = "defaults::expressions::req_limit",
+        deserialize_with = "deserializers::unsigned_minimum::<_, _, 1>"
+    )]
+    pub req_limit:      u32,
+    /// Interval when hitting the request limit
+    #[serde(
+        default = "defaults::expressions::req_interval",
+        deserialize_with = "deserializers::suffix_interval"
+    )]
+    pub req_interval:   u32,
     /// Action to take when banning a user
     #[serde(default = "defaults::expressions::ban_action")]
     pub ban_action:     BanAction,
@@ -267,6 +289,8 @@
             exclude:           Vec::new(),
             source_id:         Vec::new(),
             source_id_exclude: Vec::new(),
+            check_tokens:      true,
+            check_oauth2:      true,
             days:              defaults::inactive::days(),
             req_limit:         defaults::inactive::req_limit(),
             req_interval:      defaults::inactive::req_interval(),
@@ -282,6 +306,8 @@
             ban_alert:      false,
             interval:       defaults::expressions::interval(),
             limit:          defaults::expressions::limit(),
+            req_limit:      defaults::expressions::req_limit(),
+            req_interval:   defaults::expressions::req_interval(),
             ban_action:     defaults::expressions::ban_action(),
             ban:            Expr::default(),
             sus:            Expr::default(),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/forgejo-guardian-0.4.1/src/forgejo_api/activity_feed.rs 
new/forgejo-guardian-0.5.0/src/forgejo_api/activity_feed.rs
--- old/forgejo-guardian-0.4.1/src/forgejo_api/activity_feed.rs 2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/forgejo_api/activity_feed.rs 2025-01-28 
20:22:43.000000000 +0100
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2024-2025 Awiteb <a...@4rs.nl>
+
 use reqwest::{Client, Method};
 use url::Url;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/forgejo_api/mod.rs 
new/forgejo-guardian-0.5.0/src/forgejo_api/mod.rs
--- old/forgejo-guardian-0.4.1/src/forgejo_api/mod.rs   2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/forgejo_api/mod.rs   2025-01-28 
20:22:43.000000000 +0100
@@ -6,12 +6,14 @@
 mod activity_feed;
 mod ban_user;
 mod get_users;
+mod tokens;
 mod user;
 
 pub use activity_feed::*;
 pub use ban_user::*;
 pub use get_users::*;
 use reqwest::{Method, Request};
+pub use tokens::*;
 pub use user::*;
 
 /// Build a request with the given method, instance, token and endpoint.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/forgejo_api/tokens.rs 
new/forgejo-guardian-0.5.0/src/forgejo_api/tokens.rs
--- old/forgejo-guardian-0.4.1/src/forgejo_api/tokens.rs        1970-01-01 
01:00:00.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/forgejo_api/tokens.rs        2025-01-28 
20:22:43.000000000 +0100
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// Copyright (C) 2024-2025 Awiteb <a...@4rs.nl>
+
+use reqwest::{Client, Method};
+use url::Url;
+
+use crate::{
+    error::{GuardError, GuardResult},
+    forgejo_api,
+};
+
+/// Returns whether the tokens is empty.
+pub async fn is_empty_tokens(
+    client: &Client,
+    instance: &Url,
+    token: &str,
+    username: &str,
+) -> GuardResult<bool> {
+    let req = forgejo_api::build_request(
+        Method::GET,
+        instance,
+        token,
+        &format!("/api/v1/users/{username}/tokens"),
+    );
+    let url = req.url().clone();
+    let res = client.execute(req).await?;
+
+    if !res.status().is_success() {
+        return Err(GuardError::InvalidForgejoResponse(
+            format!("Status code: {status}", status = res.status()),
+            url,
+        ));
+    }
+
+    tracing::debug!("Get tokens response: {res:?}");
+
+    Ok(res.text().await.unwrap_or_default().trim() == "[]")
+}
+
+/// Returns whether the apps is empty
+pub async fn is_empty_apps(
+    client: &Client,
+    instance: &Url,
+    token: &str,
+    username: &str,
+) -> GuardResult<bool> {
+    let req = forgejo_api::build_request(
+        Method::GET,
+        instance,
+        token,
+        &format!("/api/v1/user/applications/oauth2?sudo={username}"),
+    );
+    let url = req.url().clone();
+    let res = client.execute(req).await?;
+
+    if !res.status().is_success() {
+        return Err(GuardError::InvalidForgejoResponse(
+            format!("Status code: {status}", status = res.status()),
+            url,
+        ));
+    }
+
+    tracing::debug!("Get apps response: {res:?}");
+
+    Ok(res.text().await.unwrap_or_default().trim() == "[]")
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/inactive_users.rs 
new/forgejo-guardian-0.5.0/src/inactive_users.rs
--- old/forgejo-guardian-0.4.1/src/inactive_users.rs    2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/inactive_users.rs    2025-01-28 
20:22:43.000000000 +0100
@@ -8,6 +8,7 @@
 
 use reqwest::Client;
 use tokio_util::sync::CancellationToken;
+use url::Url;
 
 use crate::{
     config::{BanAction, Config},
@@ -16,6 +17,65 @@
 
 const LIMIT: u32 = 30;
 
+/// Returns true if the user has no tokens or `is_enabled` is false.
+///
+/// If there is an error while fetching the tokens, it will return false.
+async fn is_empty_tokens(
+    client: &Client,
+    instance: &Url,
+    token: &str,
+    username: &str,
+    is_enabled: bool,
+) -> bool {
+    if !is_enabled {
+        return true;
+    }
+
+    match forgejo_api::is_empty_tokens(client, instance, token, 
username).await {
+        Ok(value) => value,
+        Err(err) => {
+            tracing::error!("Error while get user `@{}` tokens: {err}", 
username);
+            false
+        }
+    }
+}
+
+/// Returns true if the user has no apps or `is_enabled` is false.
+///
+/// If there is an error while fetching the apps, it will return false.
+async fn is_empty_apps(
+    client: &Client,
+    instance: &Url,
+    token: &str,
+    username: &str,
+    is_enabled: bool,
+) -> bool {
+    if !is_enabled {
+        return true;
+    }
+
+    match forgejo_api::is_empty_apps(client, instance, token, username).await {
+        Ok(value) => value,
+        Err(err) => {
+            tracing::error!("Error while get user `@{}` tokens: {err}", 
username);
+            false
+        }
+    }
+}
+
+/// Returns true if the tokens and apps are empty
+async fn is_empty_tokens_and_apps(
+    client: &Client,
+    instance: &Url,
+    token: &str,
+    username: &str,
+    tokens_enabled: bool,
+    apps_enabled: bool,
+) -> bool {
+    is_empty_tokens(client, instance, token, username, tokens_enabled).await
+        && is_empty_apps(client, instance, token, username, apps_enabled).await
+}
+
 /// Check if the user is inactive.
 async fn check_user(req_client: &Client, config: &Config, user: ForgejoUser) 
-> usize {
     if user.is_admin
@@ -33,15 +93,25 @@
         return 0;
     }
 
-    match forgejo_api::is_empty_feeds(
-        req_client,
-        &config.forgejo.instance,
-        &config.forgejo.token,
-        &user.username,
-    )
-    .await
-    {
-        Ok(true) => {
+    match (
+        forgejo_api::is_empty_feeds(
+            req_client,
+            &config.forgejo.instance,
+            &config.forgejo.token,
+            &user.username,
+        )
+        .await,
+        is_empty_tokens_and_apps(
+            req_client,
+            &config.forgejo.instance,
+            &config.forgejo.token,
+            &user.username,
+            config.inactive.check_tokens,
+            config.inactive.check_oauth2,
+        )
+        .await,
+    ) {
+        (Ok(true), true) => {
             tracing::info!("User `@{}` is inactive.", user.username);
             if !config.dry_run {
                 if let Err(err) = forgejo_api::ban_user(
@@ -55,16 +125,19 @@
                 {
                     tracing::error!("Error while ban inactive user `@{}`: 
{err}", user.username);
                 }
-                return 2; // heatmap and purge request}
+                // activity feed, purge request and tokens (if sended)
+                return 2
+                    + usize::from(config.inactive.check_tokens)
+                    + usize::from(config.inactive.check_oauth2);
             }
         }
-        Err(err) => {
+        (Err(err), ..) => {
             tracing::error!("{err}");
         }
         _ => {}
     }
 
-    1 // only heatmap request
+    1 // only activity feed request
 }
 
 /// Check all the instance users and delete the inactive ones.
@@ -131,8 +204,7 @@
             }
         };
         for user in users {
-            // +2 Because the next check need 1~2 requests
-            if (reqs + 2) > config.inactive.req_limit.into() {
+            if (reqs + 4) > config.inactive.req_limit.into() {
                 if wait_interval().await {
                     tracing::warn!("Inactive users checker stopped while 
checking users.");
                     break 'main_loop;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/main.rs 
new/forgejo-guardian-0.5.0/src/main.rs
--- old/forgejo-guardian-0.4.1/src/main.rs      2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/src/main.rs      2025-01-28 20:22:43.000000000 
+0100
@@ -116,6 +116,13 @@
             sus_sender,
             ban_sender,
         ));
+
+        if !config.expressions.only_new_users {
+            tokio::spawn(users_fetcher::old_users(
+                Arc::clone(&config),
+                cancellation_token.clone(),
+            ));
+        }
     }
 
     if let Some(telegram) = config.telegram.is_enabled() {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/traits.rs 
new/forgejo-guardian-0.5.0/src/traits.rs
--- old/forgejo-guardian-0.4.1/src/traits.rs    2025-01-22 07:46:25.000000000 
+0100
+++ new/forgejo-guardian-0.5.0/src/traits.rs    2025-01-28 20:22:43.000000000 
+0100
@@ -14,6 +14,10 @@
 
 impl ExprChecker for Expr {
     fn is_match<'a>(&'a self, user: &ForgejoUser) -> Option<RegexReason> {
+        if !self.enabled {
+            return None;
+        }
+
         let one_of = |hay: &str, exprs: &'a Vec<RegexReason>| {
             // Join the user bio into a single line
             // ref: https://git.4rs.nl/awiteb/forgejo-guardian/issues/2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/users_fetcher.rs 
new/forgejo-guardian-0.5.0/src/users_fetcher.rs
--- old/forgejo-guardian-0.4.1/src/users_fetcher.rs     2025-01-22 
07:46:25.000000000 +0100
+++ new/forgejo-guardian-0.5.0/src/users_fetcher.rs     2025-01-28 
20:22:43.000000000 +0100
@@ -43,22 +43,22 @@
     .collect())
 }
 
-/// Check if ban or suspect a new user
+/// Check if ban or suspect a new user, returns `true` if the ban request 
sended
 async fn check_new_user(
     user: ForgejoUser,
     request_client: &reqwest::Client,
     config: &Config,
-    sus_sender: &Sender<(ForgejoUser, RegexReason)>,
-    ban_sender: &Sender<(ForgejoUser, RegexReason)>,
-) {
+    sus_sender: Option<&Sender<(ForgejoUser, RegexReason)>>,
+    ban_sender: Option<&Sender<(ForgejoUser, RegexReason)>>,
+) -> bool {
     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
-            if config.expressions.ban_alert {
-                ban_sender.send((user, re)).await.ok();
+            if config.expressions.ban_alert && ban_sender.is_some() {
+                ban_sender.unwrap().send((user, re)).await.ok();
             }
-            return;
+            return false;
         }
 
         if let Err(err) = forgejo_api::ban_user(
@@ -71,13 +71,15 @@
         .await
         {
             tracing::error!("Error while banning a user: {err}");
-        } else if config.expressions.ban_alert {
-            ban_sender.send((user, re)).await.ok();
+        } else if config.expressions.ban_alert && ban_sender.is_some() {
+            ban_sender.unwrap().send((user, re)).await.ok();
         }
-    } else if let Some(re) = config.expressions.sus.is_match(&user) {
+        return true;
+    } else if let Some(re) = 
sus_sender.and(config.expressions.sus.is_match(&user)) {
         tracing::info!("@{} has been suspected because `{re}`", user.username);
-        sus_sender.send((user, re)).await.ok();
+        sus_sender.unwrap().send((user, re)).await.ok();
     }
+    false
 }
 
 /// Check for new users and send the suspected users to the channel and ban the
@@ -107,12 +109,27 @@
                 last_user_id.store(uid, Ordering::Relaxed);
             }
 
-            if config.expressions.only_new_users && is_first_fetch {
+            if is_first_fetch {
                 return;
             }
 
             for user in new_users {
-                check_new_user(user, &request_client, &config, &sus_sender, 
&ban_sender).await;
+                check_new_user(
+                    user,
+                    &request_client,
+                    &config,
+                    config
+                        .telegram
+                        .is_enabled()
+                        .is_some()
+                        .then_some(&sus_sender),
+                    config
+                        .telegram
+                        .is_enabled()
+                        .is_some()
+                        .then_some(&ban_sender),
+                )
+                .await;
             }
         }
         Err(err) => {
@@ -151,3 +168,71 @@
         };
     }
 }
+
+/// Check for old users and ban them if they match the ban expressions. This
+/// will not sned any alerts
+pub async fn old_users(config: Arc<Config>, cancellation_token: 
CancellationToken) {
+    tracing::info!("Starting old users fetcher");
+
+    let wait_interval = || {
+        async {
+            tracing::debug!(
+                "Reached the request limit for old users checker. Waiting for 
{} seconds.",
+                config.expressions.req_interval
+            );
+            tokio::select! {
+                _ = 
tokio::time::sleep(Duration::from_secs(config.expressions.req_interval.into())) 
=> false,
+                _ = cancellation_token.cancelled() => true,
+            }
+        }
+    };
+
+    let client = reqwest::Client::new();
+    let mut reqs = 0;
+    let mut page = 1;
+
+    'main_loop: loop {
+        // Enter the block if we cancelled, so will break
+        if reqs >= config.expressions.req_limit || 
cancellation_token.is_cancelled() {
+            if wait_interval().await {
+                break;
+            }
+            reqs = 0
+        }
+        reqs += 1;
+
+        let Ok(users) = forgejo_api::get_users(
+            &client,
+            &config.forgejo.instance,
+            &config.forgejo.token,
+            config.expressions.limit,
+            page,
+            "oldest",
+        )
+        .await
+        else {
+            tracing::error!("Falid to fetch old users");
+            continue;
+        };
+
+        if users.is_empty() {
+            tracing::info!("No more old users to check, all instance users are 
checked.");
+            break;
+        }
+
+        for user in users {
+            if reqs >= config.expressions.req_limit || 
cancellation_token.is_cancelled() {
+                if wait_interval().await {
+                    break 'main_loop;
+                }
+                reqs = 0;
+            }
+
+            if check_new_user(user, &client, &config, None, None).await {
+                reqs += 1;
+            }
+        }
+
+        page += 1;
+    }
+}

++++++ forgejo-guardian.obsinfo ++++++
--- /var/tmp/diff_new_pack.L9FkpA/_old  2025-01-29 16:19:04.037089890 +0100
+++ /var/tmp/diff_new_pack.L9FkpA/_new  2025-01-29 16:19:04.041090056 +0100
@@ -1,5 +1,5 @@
 name: forgejo-guardian
-version: 0.4.1
-mtime: 1737528385
-commit: 1e76bd0a05771dfe53d0e4d15f68e7657a705334
+version: 0.5.0
+mtime: 1738092163
+commit: e82f4a1e1f195f0778eb52ee6a87181f4bbc60e4
 

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/forgejo-guardian/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316/vendor.tar.zst differ: 
char 7, line 1

Reply via email to