use chrono::{DateTime, Utc};
use serde::Deserialize;
use super::snowflake;
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Bot {
#[serde(deserialize_with = "snowflake::as_string::deserialize")]
pub id: u64,
#[serde(
deserialize_with = "snowflake::option_as_string::deserialize",
rename = "topGGId"
)]
pub topgg_id: Option<u64>,
#[serde(deserialize_with = "snowflake::vec_as_string::deserialize")]
pub owners: Vec<u64>,
pub deleted: bool,
pub name: String,
#[serde(rename = "def_avatar")]
pub default_avatar: Option<String>,
pub avatar: Option<String>,
#[serde(rename = "short_desc")]
pub short_description: String,
pub prefix: String,
pub website: Option<String>,
#[serde(rename = "approved_at")]
pub approved_at: DateTime<Utc>,
#[serde(default)]
pub tags: Vec<String>,
pub monthly_votes: i64,
pub total_votes: i64,
pub server_count: Option<i64>,
pub review_count: Option<i64>,
pub avg_review_rating: Option<f64>,
pub monthly_votes_rank: i64,
pub server_count_rank: Option<i64>,
pub total_votes_rank: i64,
pub timestamp: DateTime<Utc>,
pub unix_timestamp: Option<String>,
#[serde(rename = "percentage_changes")]
pub percentage_changes: Option<PercentageChanges>,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct PercentageChanges {
pub daily: Option<f64>,
pub monthly: Option<f64>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct PartialBot {
#[serde(deserialize_with = "snowflake::as_string::deserialize")]
pub id: u64,
pub name: String,
pub monthly_votes: i64,
pub total_votes: i64,
pub server_count: Option<i64>,
pub review_count: Option<i64>,
pub monthly_votes_rank: i64,
pub server_count_rank: Option<i64>,
pub total_votes_rank: i64,
}
impl Bot {
#[must_use]
pub const fn validate_id(id: u64) -> bool {
id >= 10_000_000_000_000_000
}
#[must_use]
pub const fn created_at(&self) -> Option<DateTime<Utc>> {
snowflake_to_datetime(self.id)
}
}
impl PartialBot {
#[must_use]
pub const fn created_at(&self) -> Option<DateTime<Utc>> {
snowflake_to_datetime(self.id)
}
}
const DISCORD_EPOCH: i64 = 1_420_070_400_000;
const fn snowflake_to_datetime(id: u64) -> Option<DateTime<Utc>> {
#[allow(clippy::cast_possible_wrap)]
let timestamp_ms = ((id >> 22) as i64) + DISCORD_EPOCH;
DateTime::from_timestamp_millis(timestamp_ms)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_bot_id() {
assert!(Bot::validate_id(432_610_292_342_587_392)); assert!(Bot::validate_id(10_000_000_000_000_000)); assert!(Bot::validate_id(1_234_567_890_123_456_789));
assert!(!Bot::validate_id(123)); assert!(!Bot::validate_id(0)); assert!(!Bot::validate_id(9_999_999_999_999_999)); }
#[test]
fn test_snowflake_to_datetime() {
let dt = snowflake_to_datetime(432_610_292_342_587_392).unwrap();
assert_eq!(dt.year(), 2018);
assert_eq!(dt.month(), 4);
}
#[test]
fn test_bot_deserialization() {
let json = r#"{
"id": "583807014896140293",
"topGGId": "583807014896140293",
"owners": ["321714991050784770"],
"deleted": false,
"name": "TopStats",
"def_avatar": "1.png",
"short_desc": "Get statistics of every bot",
"prefix": "/",
"website": "https://topstats.gg",
"approved_at": "2019-05-30T00:00:00.000Z",
"tags": ["bot-statistics", "rankings"],
"monthly_votes": 4,
"total_votes": 278,
"server_count": 364,
"review_count": 0,
"monthly_votes_rank": 1649,
"server_count_rank": 3420,
"total_votes_rank": 4491,
"timestamp": "2024-10-28T18:00:00.000Z"
}"#;
let bot: Bot = serde_json::from_str(json).unwrap();
assert_eq!(bot.id, 583_807_014_896_140_293);
assert_eq!(bot.name, "TopStats");
assert_eq!(bot.monthly_votes, 4);
assert_eq!(bot.owners.len(), 1);
assert_eq!(bot.owners[0], 321_714_991_050_784_770);
assert!(!bot.deleted);
assert_eq!(bot.tags.len(), 2);
}
#[test]
fn test_partial_bot_deserialization() {
let json = r#"{
"id": "432610292342587392",
"name": "Mudae",
"monthly_votes": 2312207,
"monthly_votes_rank": 1,
"server_count": 3371839,
"server_count_rank": 13,
"total_votes": 205265098,
"total_votes_rank": 1
}"#;
let bot: PartialBot = serde_json::from_str(json).unwrap();
assert_eq!(bot.id, 432_610_292_342_587_392);
assert_eq!(bot.name, "Mudae");
assert_eq!(bot.monthly_votes_rank, 1);
}
use chrono::Datelike;
}