From bf841ab11481c83fcfbf62b1e7222d793cd26288 Mon Sep 17 00:00:00 2001 From: null8626 Date: Fri, 15 Dec 2023 22:53:14 +0700 Subject: [PATCH] feat: autoposter handles! --- Cargo.toml | 2 +- README.md | 5 +- src/autoposter.rs | 161 ++++++++++++++++++++++++++++------------------ src/bot.rs | 96 +++++++++------------------ src/client.rs | 155 +++++++++++++++++--------------------------- src/lib.rs | 2 +- src/user.rs | 46 +++++-------- 7 files changed, 212 insertions(+), 255 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 446f193..43d33ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "topgg" -version = "1.2.0" +version = "1.2.1" edition = "2021" authors = ["null (https://github.com/null8626)", "Top.gg (https://top.gg)"] description = "The official Rust wrapper for the Top.gg API" diff --git a/README.md b/README.md index dcde511..7c669da 100644 --- a/README.md +++ b/README.md @@ -164,9 +164,8 @@ async fn main() { // ... then in some on ready/new guild event ... let server_count = 12345; - autoposter - .feed(Stats::count_based(server_count, None)) - .await; + let stats = Stats::count_based(server_count, None); + autoposter.feed(stats).await; } ``` diff --git a/src/autoposter.rs b/src/autoposter.rs index c7cda41..40fdcd7 100644 --- a/src/autoposter.rs +++ b/src/autoposter.rs @@ -1,5 +1,5 @@ use crate::{client::InnerClient, Stats}; -use core::{mem::MaybeUninit, time::Duration}; +use core::{mem::MaybeUninit, ops::Deref, time::Duration}; use std::sync::Arc; use tokio::{ sync::Mutex, @@ -12,47 +12,103 @@ struct PendingData { stats: Stats, } +/// A fully [`Clone`]able and thread-safe struct that lets you remotely feed bot statistics to the [`Autoposter`]. +pub struct AutoposterHandle { + data: Arc>, +} + +impl AutoposterHandle { + /// Feeds new bot stats to this autoposter handle. The [autoposter itself][Autoposter] will automatically post it to the [Top.gg](https://top.gg) servers once appropiate. + /// + /// # Examples + /// + /// Direct usage with an [`Autoposter`]: + /// + /// ```rust,no_run + /// use core::time::Duration; + /// use topgg::{Client, Stats}; + /// + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). + /// // the autopost thread will stop once it's dropped. + /// let autoposter = client.new_autoposter(Duration::from_secs(1800)); + /// + /// // ... then in some on ready/new guild event ... + /// let server_count = 12345; + /// let stats = Stats::count_based(server_count, None); + /// autoposter.feed(stats).await; + /// ``` + /// + /// Remote usage with an [`AutoposterHandle`]: + /// + /// ```rust,no_run + /// use core::time::Duration; + /// use topgg::{Client, Stats}; + /// + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). + /// // the autopost thread will stop once it's dropped. + /// let autoposter = client.new_autoposter(Duration::from_secs(1800)); + /// + /// let server_count = 12345; + /// autoposter + /// .feed(Stats::count_based(server_count, None)) + /// .await; + /// + /// // this handle can be cloned and tossed around threads! + /// let new_handle = autoposter.handle(); + /// + /// // do the same thing... + /// new_handle + /// .feed(Stats::count_based(server_count, None)) + /// .await; + /// + /// let another_handle = new_handle.clone(); + /// + /// // do the same thing... + /// another_handle + /// .feed(Stats::count_based(server_count, None)) + /// .await; + /// ``` + pub async fn feed(&self, new_stats: Stats) { + let mut lock = self.data.lock().await; + + lock.ready = true; // flag the PendingData object as containing new data. + lock.stats = new_stats; + } +} + +/// Creates another handle that points to the same reference handle. Somewhat similar to an [`Arc::clone`]. +impl Clone for AutoposterHandle { + #[inline(always)] + fn clone(&self) -> Self { + Self { + data: Arc::clone(&self.data), + } + } +} + /// A struct that lets you automate the process of posting bot statistics to the [Top.gg API](https://docs.top.gg) in intervals. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ```rust,no_run -/// use core::time::Duration; -/// use topgg::{Client, Stats}; -/// -/// #[tokio::main] -/// async fn main() { -/// let client = Client::new(env!("TOPGG_TOKEN").to_string()); -/// -/// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). -/// // the autopost thread will stop once it's dropped. -/// let autoposter = client.new_autoposter(Duration::from_secs(1800)); -/// -/// // ... then in some on ready/new guild event ... -/// let server_count = 12345; -/// autoposter -/// .feed(Stats::count_based(server_count, None)) -/// .await; -/// } -/// ``` #[must_use] pub struct Autoposter { thread: JoinHandle<()>, - data: Arc>, + handle: AutoposterHandle, } impl Autoposter { #[allow(invalid_value, clippy::uninit_assumed_init)] pub(crate) fn new(client: Arc, interval: Duration) -> Self { // SAFETY: post_stats will be called ONLY when the ready flag is set to true. - let current_thread_data = Arc::new(Mutex::new(PendingData { - ready: false, - stats: unsafe { MaybeUninit::uninit().assume_init() }, - })); + let handle = AutoposterHandle { + data: Arc::new(Mutex::new(PendingData { + ready: false, + stats: unsafe { MaybeUninit::uninit().assume_init() }, + })), + }; - let thread_data = Arc::clone(¤t_thread_data); + let thread_data = Arc::clone(&handle.data); Self { thread: spawn(async move { @@ -67,40 +123,23 @@ impl Autoposter { } } }), - data: current_thread_data, + handle, } } - /// Feeds new bot stats to the autoposter. The autoposter will automatically post it to the [Top.gg](https://top.gg) servers in intervals. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ```rust,no_run - /// use core::time::Duration; - /// use topgg::{Client, Stats}; - /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). - /// // the autopost thread will stop once it's dropped. - /// let autoposter = client.new_autoposter(Duration::from_secs(1800)); - /// - /// // ... then in some on ready/new guild event ... - /// let server_count = 12345; - /// autoposter - /// .feed(Stats::count_based(server_count, None)) - /// .await; - /// } - /// ``` - pub async fn feed(&self, new_stats: Stats) { - let mut lock = self.data.lock().await; + /// Creates an [`AutoposterHandle`] that lets you remotely feed bot statistics to this [`Autoposter`]. This struct is fully [`Clone`]able and thread-safe. + #[inline(always)] + pub fn handle(&self) -> AutoposterHandle { + self.handle.clone() + } +} - lock.ready = true; // flag the PendingData object as containing new data. - lock.stats = new_stats; +impl Deref for Autoposter { + type Target = AutoposterHandle; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.handle } } diff --git a/src/bot.rs b/src/bot.rs index 5dc5aad..c100afa 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -120,14 +120,10 @@ impl Bot { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// println!("{}", bot.created_at()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// println!("{}", bot.created_at()); /// ``` #[must_use] #[inline(always)] @@ -146,14 +142,10 @@ impl Bot { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// println!("{}", bot.avatar()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// println!("{}", bot.avatar()); /// ``` #[must_use] #[inline(always)] @@ -170,14 +162,10 @@ impl Bot { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// println!("{}", bot.invite()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// println!("{}", bot.invite()); /// ``` #[must_use] pub fn invite(&self) -> String { @@ -199,14 +187,10 @@ impl Bot { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// println!("{}", bot.shard_count()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// println!("{}", bot.shard_count()); /// ``` #[must_use] #[inline(always)] @@ -223,14 +207,10 @@ impl Bot { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// println!("{}", bot.url()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// println!("{}", bot.url()); /// ``` #[must_use] #[inline(always)] @@ -360,14 +340,10 @@ impl Stats { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let stats = client.get_stats().await.unwrap(); - /// - /// println!("{:?}", stats.shards()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let stats = client.get_stats().await.unwrap(); + /// + /// println!("{:?}", stats.shards()); /// ``` #[must_use] #[inline(always)] @@ -387,14 +363,10 @@ impl Stats { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let stats = client.get_stats().await.unwrap(); - /// - /// println!("{:?}", stats.shard_count()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let stats = client.get_stats().await.unwrap(); + /// + /// println!("{:?}", stats.shard_count()); /// ``` #[must_use] #[inline(always)] @@ -414,14 +386,10 @@ impl Stats { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let stats = client.get_stats().await.unwrap(); - /// - /// println!("{:?}", stats.server_count()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let stats = client.get_stats().await.unwrap(); + /// + /// println!("{:?}", stats.server_count()); /// ``` #[must_use] pub fn server_count(&self) -> Option { diff --git a/src/client.rs b/src/client.rs index d30a525..70020e4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -180,10 +180,7 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let _client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// } + /// let _client = Client::new(env!("TOPGG_TOKEN").to_string()); /// ``` #[inline(always)] pub fn new(token: String) -> Self { @@ -221,17 +218,13 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let user = client.get_user(661200758510977084).await.unwrap(); - /// - /// assert_eq!(user.username, "null"); - /// assert_eq!(user.id, 661200758510977084); - /// - /// println!("{:?}", user); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let user = client.get_user(661200758510977084).await.unwrap(); + /// + /// assert_eq!(user.username, "null"); + /// assert_eq!(user.id, 661200758510977084); + /// + /// println!("{:?}", user); /// ``` pub async fn get_user(&self, id: I) -> Result where @@ -265,18 +258,14 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let bot = client.get_bot(264811613708746752).await.unwrap(); - /// - /// assert_eq!(bot.username, "Luca"); - /// assert_eq!(bot.discriminator, "1375"); - /// assert_eq!(bot.id, 264811613708746752); - /// - /// println!("{:?}", bot); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let bot = client.get_bot(264811613708746752).await.unwrap(); + /// + /// assert_eq!(bot.username, "Luca"); + /// assert_eq!(bot.discriminator, "1375"); + /// assert_eq!(bot.id, 264811613708746752); + /// + /// println!("{:?}", bot); /// ``` pub async fn get_bot(&self, id: I) -> Result where @@ -298,7 +287,6 @@ impl Client { /// Errors if any of the following conditions are met: /// - An internal error from the client itself preventing it from sending a HTTP request to the [Top.gg](https://top.gg) ([`InternalClientError`][crate::Error::InternalClientError]) /// - An unexpected response from the [Top.gg](https://top.gg) servers ([`InternalServerError`][crate::Error::InternalServerError]) - /// - The requested Discord bot is not listed on [Top.gg](https://top.gg) ([`NotFound`][crate::Error::NotFound]) /// - The client is being ratelimited from sending more HTTP requests ([`Ratelimit`][crate::Error::Ratelimit]) /// /// # Examples @@ -308,14 +296,10 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let stats = client.get_stats().await.unwrap(); - /// - /// println!("{:?}", stats); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let stats = client.get_stats().await.unwrap(); + /// + /// println!("{:?}", stats); /// ``` #[inline(always)] pub async fn get_stats(&self) -> Result { @@ -342,16 +326,13 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); /// - /// let server_count = 12345; - /// client - /// .post_stats(Stats::count_based(server_count, None)) - /// .await - /// .unwrap(); - /// } + /// let server_count = 12345; + /// client + /// .post_stats(Stats::count_based(server_count, None)) + /// .await + /// .unwrap(); /// ``` #[inline(always)] pub async fn post_stats(&self, new_stats: Stats) -> Result<()> { @@ -374,19 +355,16 @@ impl Client { /// use core::time::Duration; /// use topgg::{Client, Stats}; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); /// - /// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). - /// // the autopost thread will stop once it's dropped. - /// let autoposter = client.new_autoposter(Duration::from_secs(1800)); + /// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes). + /// // the autopost thread will stop once it's dropped. + /// let autoposter = client.new_autoposter(Duration::from_secs(1800)); /// - /// // ... then in some on ready/new guild event ... - /// let server_count = 12345; - /// let stats = Stats::count_based(server_count, None); - /// autoposter.feed(stats).await; - /// } + /// // ... then in some on ready/new guild event ... + /// let server_count = 12345; + /// let stats = Stats::count_based(server_count, None); + /// autoposter.feed(stats).await; /// ``` #[cfg(feature = "autoposter")] #[cfg_attr(docsrs, doc(cfg(feature = "autoposter")))] @@ -423,13 +401,10 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// for voter in client.get_voters().await.unwrap() { - /// println!("{:?}", voter); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// for voter in client.get_voters().await.unwrap() { + /// println!("{:?}", voter); /// } /// ``` #[inline(always)] @@ -450,7 +425,6 @@ impl Client { /// Errors if any of the following conditions are met: /// - An internal error from the client itself preventing it from sending a HTTP request to the [Top.gg](https://top.gg) ([`InternalClientError`][crate::Error::InternalClientError]) /// - An unexpected response from the [Top.gg](https://top.gg) servers ([`InternalServerError`][crate::Error::InternalServerError]) - /// - The requested Discord bot is not listed on [Top.gg](https://top.gg) ([`NotFound`][crate::Error::NotFound]) /// - The client is being ratelimited from sending more HTTP requests ([`Ratelimit`][crate::Error::Ratelimit]) /// /// # Examples @@ -460,24 +434,21 @@ impl Client { /// ```rust,no_run /// use topgg::{Client, Query}; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// // inputting a string searches a bot that matches that username. - /// for bot in client.get_bots("shiro").await.unwrap() { - /// println!("{:?}", bot); - /// } - /// - /// let query = Query::new() - /// .limit(250) - /// .skip(50) - /// .username("shiro") - /// .certified(true); - /// - /// for bot in client.get_bots(query).await.unwrap() { - /// println!("{:?}", bot); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// // inputting a string searches a bot that matches that username. + /// for bot in client.get_bots("shiro").await.unwrap() { + /// println!("{:?}", bot); + /// } + /// + /// let query = Query::new() + /// .limit(250) + /// .skip(50) + /// .username("shiro") + /// .certified(true); + /// + /// for bot in client.get_bots(query).await.unwrap() { + /// println!("{:?}", bot); /// } /// ``` pub async fn get_bots(&self, query: Q) -> Result> @@ -515,13 +486,10 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); /// - /// if client.has_voted(661200758510977084).await.unwrap() { - /// println!("checks out"); - /// } + /// if client.has_voted(661200758510977084).await.unwrap() { + /// println!("checks out"); /// } /// ``` #[allow(clippy::transmute_int_to_bool)] @@ -559,13 +527,10 @@ impl Client { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// if client.is_weekend().await.unwrap() { - /// println!("guess what? it's the weekend! woohoo! 🎉🎉🎉🎉"); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// if client.is_weekend().await.unwrap() { + /// println!("guess what? it's the weekend! woohoo! 🎉🎉🎉🎉"); /// } /// ``` #[inline(always)] diff --git a/src/lib.rs b/src/lib.rs index 95f882a..b5a2d2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ cfg_if::cfg_if! { mod autoposter; #[cfg_attr(docsrs, doc(cfg(feature = "autoposter")))] - pub use autoposter::Autoposter; + pub use autoposter::{Autoposter, AutoposterHandle}; } } diff --git a/src/user.rs b/src/user.rs index 672fcc5..54fb728 100644 --- a/src/user.rs +++ b/src/user.rs @@ -99,14 +99,10 @@ impl User { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let user = client.get_user(661200758510977084).await.unwrap(); - /// - /// println!("{}", user.created_at()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let user = client.get_user(661200758510977084).await.unwrap(); + /// + /// println!("{}", user.created_at()); /// ``` #[must_use] #[inline(always)] @@ -125,14 +121,10 @@ impl User { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// let user = client.get_user(661200758510977084).await.unwrap(); - /// - /// println!("{}", user.avatar()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// let user = client.get_user(661200758510977084).await.unwrap(); + /// + /// println!("{}", user.avatar()); /// ``` #[must_use] #[inline(always)] @@ -190,13 +182,10 @@ impl Voter { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// for voter in client.get_voters().await.unwrap() { - /// println!("{}", voter.created_at()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// for voter in client.get_voters().await.unwrap() { + /// println!("{}", voter.created_at()); /// } /// ``` #[must_use] @@ -216,13 +205,10 @@ impl Voter { /// ```rust,no_run /// use topgg::Client; /// - /// #[tokio::main] - /// async fn main() { - /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); - /// - /// for voter in client.get_voters().await.unwrap() { - /// println!("{}", voter.avatar()); - /// } + /// let client = Client::new(env!("TOPGG_TOKEN").to_string()); + /// + /// for voter in client.get_voters().await.unwrap() { + /// println!("{}", voter.avatar()); /// } /// ``` #[must_use]