use crate::models::{HasId, HasName, Snowflake};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Robot {
pub id: Snowflake,
pub username: String,
pub avatar: Option<String>,
pub discriminator: Option<String>,
#[serde(default = "default_true")]
pub bot: bool,
pub status: Option<RobotStatus>,
pub activity: Option<Activity>,
}
fn default_true() -> bool {
true
}
impl Robot {
pub fn new(id: impl Into<Snowflake>, username: impl Into<String>) -> Self {
Self {
id: id.into(),
username: username.into(),
avatar: None,
discriminator: None,
bot: true,
status: None,
activity: None,
}
}
pub fn avatar_url(&self) -> Option<String> {
self.avatar.as_ref().map(|_hash| {
format!(
"https://thirdqq.qlogo.cn/headimg_dl?dst_uin={}&spec=640",
self.id
)
})
}
pub fn tag(&self) -> String {
match &self.discriminator {
Some(disc) => format!("{}#{}", self.username, disc),
None => self.username.clone(),
}
}
pub fn mention(&self) -> String {
format!("<@{}>", self.id)
}
pub fn with_status(mut self, status: RobotStatus) -> Self {
self.status = Some(status);
self
}
pub fn with_activity(mut self, activity: Activity) -> Self {
self.activity = Some(activity);
self
}
}
impl HasId for Robot {
fn id(&self) -> Option<&Snowflake> {
Some(&self.id)
}
}
impl HasName for Robot {
fn name(&self) -> &str {
&self.username
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(from = "u8", into = "u8")]
pub enum RobotStatus {
Offline,
Online,
Idle,
Dnd,
Invisible,
Unknown(u8),
}
impl From<u8> for RobotStatus {
fn from(value: u8) -> Self {
match value {
0 => Self::Offline,
1 => Self::Online,
2 => Self::Idle,
3 => Self::Dnd,
4 => Self::Invisible,
other => Self::Unknown(other),
}
}
}
impl From<RobotStatus> for u8 {
fn from(status: RobotStatus) -> Self {
match status {
RobotStatus::Offline => 0,
RobotStatus::Online => 1,
RobotStatus::Idle => 2,
RobotStatus::Dnd => 3,
RobotStatus::Invisible => 4,
RobotStatus::Unknown(value) => value,
}
}
}
impl RobotStatus {
pub fn is_available(self) -> bool {
matches!(self, RobotStatus::Online | RobotStatus::Idle)
}
pub fn is_busy(self) -> bool {
matches!(self, RobotStatus::Dnd)
}
pub fn is_offline(self) -> bool {
matches!(self, RobotStatus::Offline | RobotStatus::Invisible)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Activity {
pub name: String,
#[serde(rename = "type")]
pub activity_type: ActivityType,
pub url: Option<String>,
pub state: Option<String>,
pub details: Option<String>,
}
impl Activity {
pub fn new(name: impl Into<String>, activity_type: ActivityType) -> Self {
Self {
name: name.into(),
activity_type,
url: None,
state: None,
details: None,
}
}
pub fn playing(name: impl Into<String>) -> Self {
Self::new(name, ActivityType::Playing)
}
pub fn streaming(name: impl Into<String>, url: impl Into<String>) -> Self {
Self {
name: name.into(),
activity_type: ActivityType::Streaming,
url: Some(url.into()),
state: None,
details: None,
}
}
pub fn listening(name: impl Into<String>) -> Self {
Self::new(name, ActivityType::Listening)
}
pub fn watching(name: impl Into<String>) -> Self {
Self::new(name, ActivityType::Watching)
}
pub fn custom(name: impl Into<String>) -> Self {
Self::new(name, ActivityType::Custom)
}
pub fn with_state(mut self, state: impl Into<String>) -> Self {
self.state = Some(state.into());
self
}
pub fn with_details(mut self, details: impl Into<String>) -> Self {
self.details = Some(details.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(from = "u8", into = "u8")]
#[repr(u8)]
pub enum ActivityType {
Playing = 0,
Streaming = 1,
Listening = 2,
Watching = 3,
Custom = 4,
Competing = 5,
Unknown(u8),
}
impl From<u8> for ActivityType {
fn from(value: u8) -> Self {
match value {
0 => Self::Playing,
1 => Self::Streaming,
2 => Self::Listening,
3 => Self::Watching,
4 => Self::Custom,
5 => Self::Competing,
other => Self::Unknown(other),
}
}
}
impl From<ActivityType> for u8 {
fn from(activity_type: ActivityType) -> Self {
match activity_type {
ActivityType::Playing => 0,
ActivityType::Streaming => 1,
ActivityType::Listening => 2,
ActivityType::Watching => 3,
ActivityType::Custom => 4,
ActivityType::Competing => 5,
ActivityType::Unknown(value) => value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_robot_creation() {
let robot = Robot::new("123456789", "TestBot");
assert_eq!(robot.id, "123456789");
assert_eq!(robot.username, "TestBot");
assert!(robot.bot);
assert_eq!(robot.mention(), "<@123456789>");
}
#[test]
fn test_robot_tag() {
let mut robot = Robot::new("123456789", "TestBot");
assert_eq!(robot.tag(), "TestBot");
robot.discriminator = Some("0001".to_string());
assert_eq!(robot.tag(), "TestBot#0001");
}
#[test]
fn test_robot_status() {
assert!(RobotStatus::Online.is_available());
assert!(RobotStatus::Idle.is_available());
assert!(RobotStatus::Dnd.is_busy());
assert!(RobotStatus::Offline.is_offline());
assert!(RobotStatus::Invisible.is_offline());
}
#[test]
fn test_activity_creation() {
let activity = Activity::playing("Rust Programming");
assert_eq!(activity.name, "Rust Programming");
assert_eq!(activity.activity_type, ActivityType::Playing);
let streaming = Activity::streaming("Live Coding", "https://example.com");
assert_eq!(streaming.activity_type, ActivityType::Streaming);
assert_eq!(streaming.url, Some("https://example.com".to_string()));
}
#[test]
fn test_activity_type_conversion() {
assert_eq!(ActivityType::from(0), ActivityType::Playing);
assert_eq!(u8::from(ActivityType::Playing), 0);
assert_eq!(ActivityType::from(99), ActivityType::Unknown(99));
assert_eq!(u8::from(ActivityType::Unknown(99)), 99);
}
}