Skip to main content

topgg/
lib.rs

1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3use std::num::NonZeroU32;
4use governor::{Quota, RateLimiter, clock, state};
5
6use futures_util::future;
7use warp::Filter;
8use futures::channel::mpsc;
9use tokio::task;
10
11
12
13const BASE_URL: &str = "https://top.gg/api";
14
15
16/// This is the top.gg API client. It houses the functions needed to interact with their API.
17pub struct Topgg {
18    bot_id: u64,
19    token: String,
20    client: reqwest::Client,
21    limiter: RateLimiter<state::direct::NotKeyed, state::InMemoryState, clock::DefaultClock>
22}
23impl Topgg {
24    /// Returns a new client.
25    /// 
26    /// ## Arguments
27    /// * `bot_id` - The ID of your bot
28    /// * `token` - The top.gg token for that (or another valid) bot
29    /// 
30    /// ## Examples
31    /// ```
32    /// let client = topgg::Topgg::new(bot_id, token);
33    /// // Do stuff with the client
34    /// let votes = client.votes().await.unwrap();
35    /// ```
36    /// 
37    pub fn new(bot_id: u64, token: String) -> Topgg {
38        Topgg {
39            bot_id: bot_id,
40            token: token,
41            client: reqwest::Client::new(),
42            limiter: RateLimiter::direct(
43                Quota::per_minute(NonZeroU32::new(60u32).unwrap())
44            )
45        }
46    }
47
48
49    /// A shortcut for getting the botinfo for your own bot.
50    /// ## Examples
51    /// ```
52    /// let bot_info = client.my_bot().await.unwrap();
53    /// ```
54    pub async fn my_bot(&self) -> Option<Bot> {
55        self.bot(self.bot_id).await
56    }
57
58
59    /// Gets the info for a bot given an ID. To get the info for your own bot `client.my_bot()` can be used as a shortcut.
60    /// ## Examples
61    /// ```
62    /// let bot_info = lient.bot(668701133069352961).await.unwrap();
63    /// ```
64    pub async fn bot(&self, bot_id: u64) -> Option<Bot> {
65        self.limiter.until_ready().await;
66        println!("requesting");
67        let url = format!("{}/bots/{}", BASE_URL, bot_id);
68        let res = self.client
69            .get(&url)
70            .header("Authorization", &self.token)
71            .send()
72            .await;
73        if res.is_err() {
74            return None;
75        }
76
77        let res = res
78            .unwrap()        
79            .json::<JsonBot>()
80            .await;
81        if res.is_err() {
82            return None;
83        }
84        let res = res.unwrap();
85
86        Some( Bot {
87            id: res.id.parse::<u64>().unwrap(),
88            username: res.username,
89            discriminator: res.discriminator,
90            avatar: res.avatar,
91            def_avatar: res.defAvatar,
92            lib: res.lib,
93            prefix: res.prefix,
94            short_desc: res.shortdesc,
95            long_desc: res.longdesc,
96            tags: res.tags,
97            website: res.website,
98            support: res.support,
99            github: res.github,
100            owners: res.owners.into_iter().map(|u| u.parse::<u64>().unwrap()).collect(),
101            guilds: res.guilds.into_iter().map(|u| u.parse::<u64>().unwrap()).collect(),
102            invite: res.invite,
103            date: res.date,
104            certified_bot: res.certifiedBot,
105            vanity: res.vanity,
106            points: res.points,
107            monthly_points: res.monthlyPoints,
108            donate_bot_guild_id: res.donatebotguildid.parse::<u64>().ok()
109        })
110    }
111
112
113    /// Gets the info for a user.
114    /// ## Examples
115    /// ```
116    /// client.user(195512978634833920).await.unwrap();
117    /// ```
118    pub async fn user(&self, user_id: u64) -> Option<User> {
119        self.limiter.until_ready().await;
120        let url = format!("{}/users/{}", BASE_URL, user_id);
121        let res = self.client
122            .get(&url)
123            .header("Authorization", &self.token)
124            .send()
125            .await;
126        if res.is_err() {
127            return None;
128        }
129
130        let res = res
131            .unwrap()        
132            .json::<JsonUser>()
133            .await;
134        if res.is_err() {
135            return None;
136        }
137        let res = res.unwrap();
138
139        Some( User {
140            id: res.id.parse::<u64>().unwrap(),
141            username: res.username,
142            discriminator: res.discriminator,
143            avatar:res.avatar,
144            def_avatar: res.defAvatar,
145            bio: res.bio,
146            banner: res.banner,
147            youtube: res.social.get("youtube").map(|r| r.parse::<String>().unwrap()),
148            reddit: res.social.get("reddit").map(|r| r.parse::<String>().unwrap()),
149            twitter: res.social.get("twitter").map(|r| r.parse::<String>().unwrap()),
150            instagram: res.social.get("instagram").map(|r| r.parse::<String>().unwrap()),
151            github: res.social.get("github").map(|r| r.parse::<String>().unwrap()),
152            color: res.color,
153            supporter: res.supporter,
154            certified_dev: res.certifiedDev,
155            moderator: res.r#mod,
156            web_moderator: res.webMod,
157            admin: res.admin,
158        })
159    }
160
161
162    /// A shortcut for getting the votes for the bot that created the client.
163    /// ## Examples
164    /// ```
165    /// let votes = client.my_votes().await.unwrap();
166    /// ```
167    pub async fn my_votes(&self) -> Option<Vec<u64>> {
168        self.votes(self.bot_id).await
169    }
170
171
172    /// Gets the user IDs of all the users that have voted on the bot_id.
173    /// ## Examples
174    /// ```
175    /// client.votes(668701133069352961).await.unwrap();
176    /// ```
177    pub async fn votes(&self, bot_id: u64) -> Option<Vec<u64>> {
178        self.limiter.until_ready().await;
179        let url = format!("{}/bots/{}/votes", BASE_URL, bot_id);
180        let res = self.client
181            .get(&url)
182            .header("Authorization", &self.token)
183            .send()
184            .await;
185        if res.is_err() {
186            return None;
187        }
188
189        let res = res
190            .unwrap()        
191            .json::<Vec<PartialJsonUser>>()
192            .await;
193        if res.is_err() {
194            return None;
195        }
196        let res = res.unwrap();
197
198        Some(
199            res.into_iter()
200                .map(|u| u.id.parse::<u64>().unwrap())
201                .collect()
202        )
203    }
204
205
206    /// A shortcut for checking if a user has voted for your own bot.
207    /// ## Examples
208    /// ```
209    /// let voted = client.voted_for_me(195512978634833920).await.unwrap();
210    /// ```
211    pub async fn voted_for_me(&self, user_id: u64) -> Option<bool> {
212        self.voted(self.bot_id, user_id).await
213    }
214
215
216    /// Checks if a user has voted for the bot or not. Returns true if they have, false if they have not.
217    /// ## Examples
218    /// ```
219    /// let voted = client.voted(668701133069352961, 195512978634833920)
220    ///     .await
221    ///     .unwrap();
222    /// ```
223    pub async fn voted(&self, bot_id: u64, user_id: u64) -> Option<bool> {
224        self.limiter.until_ready().await;
225        let url = format!("{}/bots/{}/check?userId={}", BASE_URL, bot_id, user_id);
226        let res = self.client
227            .get(&url)
228            .header("Authorization", &self.token)
229            .send()
230            .await;
231        if res.is_err() {
232            return None;
233        }
234
235        let res = res
236            .unwrap()        
237            .json::<CheckVote>()
238            .await;
239        if res.is_err() {
240            return None;
241        }
242        let res = res.unwrap();
243
244        if res.voted == 0 {
245            return Some(false);
246        } else {
247            return Some(true);
248        }
249    }
250
251
252    /// A shortcut for getting the bot stats of the bot that created the client.
253    /// ## Examples
254    /// ```
255    /// let stats = client.my_bot_stats().await.unwrap();
256    /// ```
257    pub async fn my_bot_stats(&self) -> Option<BotStats> {
258        self.get_bot_stats(self.bot_id).await
259    }
260
261
262    /// Gets the 'stats' of the bot, this includes the server count, shard count, and shards (servers per shard).
263    /// ## Examples
264    /// ```
265    /// client.get_bot_stats(Some(668701133069352961)).await.unwrap();
266    /// ```
267    pub async fn get_bot_stats(&self, bot_id: u64) -> Option<BotStats> {
268        self.limiter.until_ready().await;
269        let url = format!("{}/bots/{}/stats", BASE_URL, bot_id);
270        let res = self.client
271            .get(&url)
272            .header("Authorization", &self.token)
273            .send()
274            .await;
275        if res.is_err() {
276            return None;
277        }
278
279        let res = res
280            .unwrap()        
281            .json::<BotStats>()
282            .await;
283        if res.is_err() {
284            return None;
285        }
286        let res = res.unwrap();
287
288        Some(res)
289    }
290
291    
292    /// This posts the stats for your bot. Useful if you want to update the server count on your top.gg bot page. You can omit from having a `server_count` if you use `shards` where it is a Vec of the number of servers per shard. `shard_id` is only applicable if you use `sever_count` and it tells top.gg the number of servers for that indexed shard.
293    /// ## Examples
294    /// ```
295    /// client.post_bot_stats(None, Some(vec![142, 532, 304]), None, None).await;
296    /// client.post_bot_stats(Some(142), None, Some(0), None).await;
297    /// client.post_bot_stats(Some(978), None, None, Some(3)).await;
298    /// ```
299    pub async fn post_bot_stats(
300        &self,
301        server_count: Option<u32>,
302        shards: Option<Vec<u32>>,
303        shard_id: Option<u32>,
304        shard_count: Option<u32>
305    ) -> Result<reqwest::Response, reqwest::Error> {
306        self.limiter.until_ready().await;
307        let url = format!("{}/bots/{}/stats", BASE_URL, self.bot_id);
308        self.client
309            .post(&url)
310            .header("Authorization", &self.token)
311            .json(&PostBotStats {
312                server_count: server_count,
313                shards: shards,
314                shard_id: shard_id,
315                shard_count: shard_count,
316            })
317            .send()
318            .await
319    }
320}
321
322
323
324pub struct WebhookClient;
325impl WebhookClient {
326    /// Starts listening to a port and filtering requests with a authentication string.
327    /// ## Examples
328    /// ```rust
329    /// use futures::StreamExt;
330    /// 
331    /// #[tokio::main]
332    /// async fn main() {
333    ///     let mut events = topgg::WebhookClient::start(3030, "a-very-secret-password".to_string());
334    ///     
335    ///     while let Some(msg) = events.next().await {
336    ///         println!("{:?}", msg)
337    ///     }
338    /// }
339    /// ```
340    pub fn start(port: u16, auth: String) -> mpsc::UnboundedReceiver<Webhook> {
341
342        let filter = warp::header::<String>("authorization")
343            .and_then(move |value| {
344                if value == auth {
345                    future::ok(())
346                } else {
347                    future::err(warp::reject::custom(Unauthorized))
348                }
349            })
350            .untuple_one();
351
352        let (event_send, event_read) = mpsc::unbounded();
353
354
355        let webhook = warp::post()
356            .and(filter)
357            .and(warp::body::json())
358            .map(move |hook: Webhook| {
359                event_send.unbounded_send(hook).unwrap();
360                warp::reply()
361            });
362        
363        task::spawn(async move {
364            warp::serve(webhook).run(([0, 0, 0, 0], port)).await;
365        });
366        
367        event_read
368    }
369}
370
371
372
373#[derive(Debug)]
374struct Unauthorized;
375impl warp::reject::Reject for Unauthorized {}
376impl std::fmt::Display for Unauthorized {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        f.write_str("Unauthorized")
379    }
380}
381impl std::error::Error for Unauthorized {}
382
383
384#[derive(Debug, Deserialize, Serialize)]
385#[serde(rename_all = "camelCase")]
386pub struct Webhook {
387    pub bot: String,
388    pub user: String,
389    #[serde(rename = "type")]
390    pub kind: String,
391    pub is_weekend: bool,
392    pub query: Option<String>,
393}
394
395
396
397
398#[allow(non_snake_case)]
399#[derive(Deserialize, Debug)]
400struct JsonBot {
401    id: String,
402    username: String,
403    discriminator: String,
404    avatar: Option<String>,
405    defAvatar: String, 
406    lib: String,
407    prefix: String,
408    shortdesc: String,
409    longdesc: Option<String>,
410    tags: Vec<String>,
411    website: Option<String>,
412    support: Option<String>,
413    github: Option<String>,
414    owners: Vec<String>,
415    guilds: Vec<String>,
416    invite: Option<String>,
417    date: String,
418    certifiedBot: bool,
419    vanity: Option<String>,
420    points: u64,
421    monthlyPoints: u64,
422    donatebotguildid: String
423}
424
425#[derive(Deserialize, Debug)]
426pub struct Bot {
427    pub id: u64,
428    pub username: String,
429    pub discriminator: String,
430    pub avatar: Option<String>,
431    pub def_avatar: String,
432    pub lib: String,
433    pub prefix: String,
434    pub short_desc: String,
435    pub long_desc: Option<String>,
436    pub tags: Vec<String>,
437    pub website: Option<String>,
438    pub support: Option<String>,
439    pub github: Option<String>,
440    pub owners: Vec<u64>,
441    pub guilds: Vec<u64>,
442    pub invite: Option<String>,
443    pub date: String,
444    pub certified_bot: bool,
445    pub vanity: Option<String>,
446    pub points: u64,
447    pub monthly_points: u64,
448    pub donate_bot_guild_id: Option<u64>
449}
450
451
452#[allow(non_snake_case)]
453#[derive(Deserialize, Debug)]
454struct JsonUser {
455    id: String,
456    username: String,
457    discriminator: String,
458    avatar: Option<String>,
459    defAvatar: String,
460    bio: Option<String>,
461    banner: Option<String>,
462    social: HashMap<String, String>,
463    color: Option<String>,
464    supporter: bool,
465    certifiedDev: bool,
466    r#mod: bool,
467    webMod: bool,
468    admin: bool,
469}
470
471#[derive(Debug)]
472pub struct User {
473    pub id: u64,
474    pub username: String,
475    pub discriminator: String,
476    pub avatar: Option<String>,
477    pub def_avatar: String,
478    pub bio: Option<String>,
479    pub banner: Option<String>,
480    pub youtube: Option<String>,
481    pub reddit: Option<String>,
482    pub twitter: Option<String>,
483    pub instagram: Option<String>,
484    pub github: Option<String>, 
485    pub color: Option<String>,
486    pub supporter: bool,
487    pub certified_dev: bool,
488    pub moderator: bool,
489    pub web_moderator: bool,
490    pub admin: bool,
491}
492
493
494#[derive(Deserialize, Debug)]
495struct PartialJsonUser {
496    id: String,
497    username: String,
498    discriminator: String,
499    avatar: Option<String>
500}
501
502#[derive(Debug)]
503pub struct PartialUser {
504    pub id: u64,
505    pub username: String,
506    pub discriminator: String,
507    pub avatar: Option<String>
508}
509
510
511#[derive(Deserialize, Debug)]
512struct CheckVote {
513    voted: i8
514}
515
516
517#[derive(Deserialize, Debug)]
518pub struct BotStats {
519    pub server_count: Option<u32>,
520    pub shards: Vec<u32>,
521    pub shard_count: Option<u32>
522}
523
524
525#[derive(Serialize, Debug)]
526struct PostBotStats {
527    server_count: Option<u32>,
528    shards: Option<Vec<u32>>,
529    shard_id: Option<u32>,
530    shard_count: Option<u32>,
531}