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
16pub 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 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 pub async fn my_bot(&self) -> Option<Bot> {
55 self.bot(self.bot_id).await
56 }
57
58
59 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 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 pub async fn my_votes(&self) -> Option<Vec<u64>> {
168 self.votes(self.bot_id).await
169 }
170
171
172 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 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 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 pub async fn my_bot_stats(&self) -> Option<BotStats> {
258 self.get_bot_stats(self.bot_id).await
259 }
260
261
262 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 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 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}