dbl/
types.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5/// Newtype for bot ids.
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub struct BotId(pub u64);
8/// Newtype for user ids.
9#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub struct UserId(pub u64);
11/// Newtype for guild ids.
12#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub struct GuildId(pub u64);
14
15/// Basic user information returned by [`Client::votes`](super::Client::votes).
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
17pub struct User {
18    pub id: UserId,
19    pub username: String,
20    pub discriminator: String,
21    pub avatar: Option<String>,
22}
23
24/// Detailed user information returned by [`Client::user`](super::Client::user).
25#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct DetailedUser {
28    pub id: UserId,
29    pub username: String,
30    pub discriminator: String,
31    pub avatar: Option<String>,
32    #[serde(rename = "defAvatar")]
33    pub default_avatar: String,
34    pub bio: Option<String>,
35    pub banner: Option<String>,
36    pub social: Option<Social>,
37    pub color: Option<String>,
38    pub supporter: bool,
39    pub certified_dev: bool,
40    #[serde(rename = "mod")]
41    pub mod_: bool,
42    pub web_mod: bool,
43    pub admin: bool,
44}
45
46/// Social media accounts of the user.
47#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
48pub struct Social {
49    pub github: Option<String>,
50    pub instagram: Option<String>,
51    pub reddit: Option<String>,
52    pub twitter: Option<String>,
53    pub youtube: Option<String>,
54}
55
56/// Information about a bot.
57#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
58#[serde(rename_all = "camelCase")]
59pub struct Bot {
60    pub id: BotId,
61    pub username: String,
62    pub discriminator: String,
63    pub avatar: Option<String>,
64    #[serde(rename = "defAvatar")]
65    pub default_avatar: String,
66    pub clientid: String,
67    pub lib: String,
68    pub prefix: String,
69    #[serde(rename = "shortdesc")]
70    pub short_desc: String,
71    #[serde(rename = "longdesc")]
72    pub long_desc: Option<String>,
73    pub tags: Vec<String>,
74    pub website: Option<String>,
75    pub support: Option<String>,
76    pub github: Option<String>,
77    pub owners: Vec<UserId>,
78    pub guilds: Vec<GuildId>,
79    pub invite: Option<String>,
80    pub date: String,
81    pub certified_bot: bool,
82    pub vanity: Option<String>,
83    pub shards: Vec<u64>,
84    pub points: u64,
85    pub monthly_points: u64,
86}
87
88/// Bot's sharding stats.
89#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
90pub struct Stats {
91    pub server_count: Option<u64>,
92    pub shards: Vec<u64>,
93    pub shard_count: Option<u64>,
94}
95
96/// Used to update one or more sharding stats.
97#[derive(Debug, Serialize)]
98#[serde(untagged)]
99pub enum ShardStats {
100    Cumulative {
101        server_count: u64,
102        shard_count: Option<u64>,
103    },
104    Shard {
105        server_count: u64,
106        shard_id: u64,
107        shard_count: u64,
108    },
109    Shards {
110        shards: Vec<u64>,
111    },
112}
113
114/// Used for filtering the bot search.
115pub struct Filter(pub(crate) HashMap<&'static str, String>);
116
117impl Default for Filter {
118    fn default() -> Filter {
119        Filter::new()
120    }
121}
122
123impl Filter {
124    pub fn new() -> Filter {
125        Filter(HashMap::with_capacity(4))
126    }
127
128    pub fn limit(mut self, mut limit: u16) -> Filter {
129        if limit > 500 {
130            limit = 500;
131        }
132        self.0.insert("limit", limit.to_string());
133        self
134    }
135
136    pub fn offset(mut self, offset: u16) -> Filter {
137        self.0.insert("offset", offset.to_string());
138        self
139    }
140
141    pub fn sort<T: AsRef<str>>(mut self, field: T, ascending: bool) -> Filter {
142        let mut buf = String::new();
143        if !ascending {
144            buf.push('-');
145        }
146        buf.push_str(field.as_ref());
147        self.0.insert("sort", buf);
148        self
149    }
150
151    /// Search string. Example: `lib:serenity mod`
152    pub fn search<T: ToString>(mut self, search: T) -> Filter {
153        self.0.insert("search", search.to_string());
154        self
155    }
156}
157
158/// Search result returned by [`Client::search`](super::Client::search).
159#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
160pub struct Listing {
161    pub results: Vec<Bot>,
162    pub limit: u64,
163    pub offset: u64,
164    pub count: u64,
165    pub total: u64,
166}
167
168/// Vote received via webhook.
169#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct Webhook {
172    pub bot: BotId,
173    pub user: UserId,
174    #[serde(rename = "type")]
175    pub kind: WebhookType,
176    #[serde(default)]
177    pub is_weekend: bool,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub query: Option<String>,
180}
181
182/// Type of vote received via webhook.
183#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
184#[serde(rename_all = "lowercase")]
185pub enum WebhookType {
186    Upvote,
187    Test,
188}
189
190impl Webhook {
191    pub fn is_test(&self) -> bool {
192        std::matches!(self.kind, WebhookType::Test)
193    }
194}
195
196impl ::std::ops::Index<usize> for Listing {
197    type Output = Bot;
198
199    fn index(&self, index: usize) -> &Self::Output {
200        &self.results[index]
201    }
202}
203
204impl IntoIterator for Listing {
205    type Item = Bot;
206    type IntoIter = ::std::vec::IntoIter<Bot>;
207
208    fn into_iter(self) -> Self::IntoIter {
209        self.results.into_iter()
210    }
211}
212
213impl<'a> IntoIterator for &'a Listing {
214    type Item = &'a Bot;
215    type IntoIter = ::std::slice::Iter<'a, Bot>;
216
217    fn into_iter(self) -> Self::IntoIter {
218        self.results.iter()
219    }
220}
221
222#[derive(Deserialize)]
223pub(crate) struct UserVoted {
224    pub voted: u64,
225}
226
227#[derive(Deserialize)]
228#[serde(rename = "kebab-case")]
229pub(crate) struct Ratelimit {
230    pub retry_after: u32,
231}
232
233macro_rules! impl_snowflake {
234    ($($type:ty),*) => {
235        $(
236            impl $type {
237                pub fn as_u64(&self) -> u64 {
238                    self.0
239                }
240            }
241
242            impl ::std::fmt::Display for $type {
243                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
244                    self.0.fmt(f)
245                }
246            }
247
248            impl From<u64> for $type {
249                fn from(v: u64) -> Self {
250                    Self(v)
251                }
252            }
253
254            impl<'de> ::serde::de::Deserialize<'de> for $type {
255                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256                where
257                    D: ::serde::de::Deserializer<'de>,
258                {
259                    struct Visitor;
260
261                    impl<'de> ::serde::de::Visitor<'de> for Visitor {
262                        type Value = $type;
263
264                        fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
265                            f.write_str("identifier")
266                        }
267
268                        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
269                        where
270                            E: ::serde::de::Error,
271                        {
272                            v.parse::<u64>().map(Into::into).map_err(|_| {
273                                E::custom(format!("invalid {}: value {}", stringify!(u64), v))
274                            })
275                        }
276                    }
277
278                    deserializer.deserialize_str(Visitor)
279                }
280            }
281
282            impl ::serde::ser::Serialize for $type {
283                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
284                where
285                    S: ::serde::ser::Serializer,
286                {
287                    serializer.collect_str(&self.0)
288                }
289            }
290        )*
291    };
292}
293
294impl_snowflake!(BotId, GuildId, UserId);
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    use serde_test::Token;
301
302    #[test]
303    fn webhook_serde() {
304        let value = Webhook {
305            bot: BotId(1),
306            user: UserId(2),
307            kind: WebhookType::Test,
308            is_weekend: false,
309            query: None,
310        };
311
312        serde_test::assert_tokens(
313            &value,
314            &[
315                Token::Struct {
316                    name: "Webhook",
317                    len: 4,
318                },
319                Token::Str("bot"),
320                Token::Str("1"),
321                Token::Str("user"),
322                Token::Str("2"),
323                Token::Str("type"),
324                Token::UnitVariant {
325                    name: "WebhookType",
326                    variant: "test",
327                },
328                Token::Str("isWeekend"),
329                Token::Bool(false),
330                Token::StructEnd,
331            ],
332        );
333    }
334
335    #[test]
336    fn webhook_de_no_weekend() {
337        let value = Webhook {
338            bot: BotId(1),
339            user: UserId(2),
340            kind: WebhookType::Test,
341            is_weekend: false,
342            query: None,
343        };
344
345        serde_test::assert_de_tokens(
346            &value,
347            &[
348                Token::Struct {
349                    name: "Webhook",
350                    len: 3,
351                },
352                Token::Str("bot"),
353                Token::Str("1"),
354                Token::Str("user"),
355                Token::Str("2"),
356                Token::Str("type"),
357                Token::UnitVariant {
358                    name: "WebhookType",
359                    variant: "test",
360                },
361                Token::StructEnd,
362            ],
363        );
364    }
365}