use crate::models::{HasId, HasName, Snowflake, Timestamp};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Guild {
pub id: Option<Snowflake>,
pub name: Option<String>,
pub icon: Option<String>,
pub owner_id: Option<Snowflake>,
pub is_owner: Option<bool>,
pub member_count: Option<u32>,
pub max_members: Option<u32>,
pub description: Option<String>,
pub joined_at: Option<Timestamp>,
}
impl Guild {
pub fn new() -> Self {
Self {
id: None,
name: None,
icon: None,
owner_id: None,
is_owner: None,
member_count: None,
max_members: None,
description: None,
joined_at: None,
}
}
pub fn from_data(_api: crate::api::BotApi, id: String, data: serde_json::Value) -> Self {
Self {
id: Some(id),
name: data.get("name").and_then(|v| v.as_str()).map(String::from),
icon: data.get("icon").and_then(|v| v.as_str()).map(String::from),
owner_id: data
.get("owner_id")
.and_then(|v| v.as_str())
.map(String::from),
is_owner: data.get("is_owner").and_then(|v| v.as_bool()),
member_count: data
.get("member_count")
.and_then(|v| v.as_u64())
.map(|v| v as u32),
max_members: data
.get("max_members")
.and_then(|v| v.as_u64())
.map(|v| v as u32),
description: data
.get("description")
.and_then(|v| v.as_str())
.map(String::from),
joined_at: data
.get("joined_at")
.and_then(|v| v.as_str())
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map(|dt| dt.with_timezone(&chrono::Utc)),
}
}
pub fn icon_url(&self) -> Option<String> {
self.icon.as_ref().map(|hash| {
format!(
"https://groupprofile.qq.com/groupicon/{}/{}",
self.id.as_ref().unwrap_or(&String::new()),
hash
)
})
}
pub fn is_owned_by_current_user(&self) -> bool {
self.is_owner.unwrap_or(false)
}
pub fn get_member_count(&self) -> u32 {
self.member_count.unwrap_or(0)
}
pub fn get_max_members(&self) -> u32 {
self.max_members.unwrap_or(0)
}
pub fn is_at_member_limit(&self) -> bool {
match (self.member_count, self.max_members) {
(Some(current), Some(max)) => current >= max,
_ => false,
}
}
pub fn display_name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn has_description(&self) -> bool {
self.description
.as_ref()
.is_some_and(|desc| !desc.is_empty())
}
}
impl Default for Guild {
fn default() -> Self {
Self::new()
}
}
impl HasId for Guild {
fn id(&self) -> Option<&Snowflake> {
self.id.as_ref()
}
}
impl HasName for Guild {
fn name(&self) -> &str {
self.name.as_deref().unwrap_or("")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GuildRoles {
pub roles: Vec<GuildRole>,
pub role_num_limit: Option<String>,
}
impl GuildRoles {
pub fn new(roles: Vec<GuildRole>) -> Self {
Self {
roles,
role_num_limit: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GuildRole {
pub id: Option<Snowflake>,
pub name: Option<String>,
pub color: Option<u32>,
pub hoist: Option<bool>,
pub number: Option<u32>,
pub member_limit: Option<u32>,
}
impl GuildRole {
pub fn new() -> Self {
Self {
id: None,
name: None,
color: None,
hoist: None,
number: None,
member_limit: None,
}
}
pub fn is_hoisted(&self) -> bool {
self.hoist.unwrap_or(false)
}
pub fn color_hex(&self) -> Option<String> {
self.color.map(|c| format!("#{c:06X}"))
}
pub fn member_count(&self) -> u32 {
self.number.unwrap_or(0)
}
pub fn get_member_limit(&self) -> u32 {
self.member_limit.unwrap_or(0)
}
pub fn is_at_member_limit(&self) -> bool {
match (self.number, self.member_limit) {
(Some(current), Some(limit)) => current >= limit,
_ => false,
}
}
}
impl Default for GuildRole {
fn default() -> Self {
Self::new()
}
}
impl HasId for GuildRole {
fn id(&self) -> Option<&Snowflake> {
self.id.as_ref()
}
}
impl HasName for GuildRole {
fn name(&self) -> &str {
self.name.as_deref().unwrap_or("")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Role {
pub id: Option<Snowflake>,
pub name: Option<String>,
pub color: Option<u32>,
pub hoist: Option<bool>,
pub number: Option<u32>,
pub member_limit: Option<u32>,
}
impl Role {
pub fn new() -> Self {
Self {
id: None,
name: None,
color: None,
hoist: None,
number: None,
member_limit: None,
}
}
pub fn is_hoisted(&self) -> bool {
self.hoist.unwrap_or(false)
}
pub fn color_hex(&self) -> Option<String> {
self.color.map(|c| format!("#{c:06X}"))
}
pub fn member_count(&self) -> u32 {
self.number.unwrap_or(0)
}
pub fn get_member_limit(&self) -> u32 {
self.member_limit.unwrap_or(0)
}
pub fn is_at_member_limit(&self) -> bool {
match (self.number, self.member_limit) {
(Some(current), Some(limit)) => current >= limit,
_ => false,
}
}
}
impl Default for Role {
fn default() -> Self {
Self::new()
}
}
impl HasId for Role {
fn id(&self) -> Option<&Snowflake> {
self.id.as_ref()
}
}
impl HasName for Role {
fn name(&self) -> &str {
self.name.as_deref().unwrap_or("")
}
}
pub type Roles = Vec<Role>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Member {
pub user: Option<crate::models::User>,
pub nick: Option<String>,
pub roles: Option<Vec<Snowflake>>,
pub joined_at: Option<Timestamp>,
}
impl Member {
pub fn new() -> Self {
Self {
user: None,
nick: None,
roles: None,
joined_at: None,
}
}
pub fn display_name(&self) -> Option<&str> {
self.nick
.as_deref()
.or_else(|| self.user.as_ref().map(|u| u.username.as_str()))
}
pub fn username(&self) -> Option<&str> {
self.user.as_ref().map(|u| u.username.as_str())
}
pub fn user_id(&self) -> Option<&Snowflake> {
self.user.as_ref().map(|u| &u.id)
}
pub fn is_bot(&self) -> bool {
self.user.as_ref().is_some_and(|u| u.is_bot())
}
pub fn role_ids(&self) -> &[Snowflake] {
self.roles.as_deref().unwrap_or(&[])
}
pub fn has_role(&self, role_id: &str) -> bool {
self.role_ids().iter().any(|id| id == role_id)
}
}
impl Default for Member {
fn default() -> Self {
Self::new()
}
}
impl HasId for Member {
fn id(&self) -> Option<&Snowflake> {
self.user_id()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_guild_creation() {
let guild = Guild::new();
assert!(guild.id.is_none());
assert!(guild.name.is_none());
assert!(!guild.is_owned_by_current_user());
assert_eq!(guild.get_member_count(), 0);
assert_eq!(guild.get_max_members(), 0);
}
#[test]
fn test_guild_with_data() {
let mut guild = Guild::new();
guild.id = Some("123456789".to_string());
guild.name = Some("Test Guild".to_string());
guild.is_owner = Some(true);
guild.member_count = Some(100);
guild.max_members = Some(500);
guild.description = Some("A test guild".to_string());
assert_eq!(guild.id(), Some(&"123456789".to_string()));
assert_eq!(guild.name(), "Test Guild");
assert!(guild.is_owned_by_current_user());
assert_eq!(guild.get_member_count(), 100);
assert_eq!(guild.get_max_members(), 500);
assert!(!guild.is_at_member_limit());
assert!(guild.has_description());
assert_eq!(guild.display_name(), Some("Test Guild"));
}
#[test]
fn test_member_limit() {
let mut guild = Guild::new();
guild.member_count = Some(500);
guild.max_members = Some(500);
assert!(guild.is_at_member_limit());
guild.member_count = Some(499);
assert!(!guild.is_at_member_limit());
guild.member_count = Some(501);
assert!(guild.is_at_member_limit());
}
#[test]
fn test_icon_url() {
let mut guild = Guild::new();
assert!(guild.icon_url().is_none());
guild.id = Some("123456789".to_string());
guild.icon = Some("abc123".to_string());
let url = guild.icon_url().unwrap();
assert!(url.contains("123456789"));
assert!(url.contains("abc123"));
}
#[test]
fn test_role_creation() {
let role = Role::new();
assert!(role.id.is_none());
assert!(role.name.is_none());
assert!(!role.is_hoisted());
assert_eq!(role.member_count(), 0);
}
#[test]
fn test_role_with_data() {
let mut role = Role::new();
role.id = Some("role123".to_string());
role.name = Some("Admin".to_string());
role.color = Some(0xFF0000);
role.hoist = Some(true);
role.number = Some(5);
role.member_limit = Some(10);
assert_eq!(role.id(), Some(&"role123".to_string()));
assert_eq!(role.name(), "Admin");
assert_eq!(role.color_hex(), Some("#FF0000".to_string()));
assert!(role.is_hoisted());
assert_eq!(role.member_count(), 5);
assert_eq!(role.get_member_limit(), 10);
assert!(!role.is_at_member_limit());
}
#[test]
fn test_member_creation() {
let member = Member::new();
assert!(member.user.is_none());
assert!(member.nick.is_none());
assert_eq!(member.role_ids().len(), 0);
}
#[test]
fn test_member_with_roles() {
let mut member = Member::new();
member.roles = Some(vec!["role1".to_string(), "role2".to_string()]);
assert!(member.has_role("role1"));
assert!(member.has_role("role2"));
assert!(!member.has_role("role3"));
assert_eq!(member.role_ids().len(), 2);
}
}