use crate::api::BotApi;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ReactionTargetType {
Message = 0,
Post = 1,
Comment = 2,
Reply = 3,
}
impl From<u8> for ReactionTargetType {
fn from(value: u8) -> Self {
match value {
0 => Self::Message,
1 => Self::Post,
2 => Self::Comment,
3 => Self::Reply,
_ => Self::Message, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Emoji {
pub id: Option<String>,
pub emoji_type: Option<u8>,
}
impl Emoji {
pub fn new(data: &Value) -> Self {
Self {
id: data.get("id").and_then(|v| v.as_str()).map(String::from),
emoji_type: data.get("type").and_then(|v| v.as_u64()).map(|v| v as u8),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReactionTarget {
pub id: Option<String>,
pub target_type: Option<ReactionTargetType>,
}
impl ReactionTarget {
pub fn new(data: &Value) -> Self {
Self {
id: data.get("id").and_then(|v| v.as_str()).map(String::from),
target_type: data
.get("type")
.and_then(|v| v.as_u64())
.map(|v| ReactionTargetType::from(v as u8)),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Reaction {
#[serde(skip)]
api: BotApi,
pub user_id: Option<String>,
pub channel_id: Option<String>,
pub guild_id: Option<String>,
pub emoji: Emoji,
pub target: ReactionTarget,
pub event_id: Option<String>,
}
impl Reaction {
pub fn new(api: BotApi, event_id: Option<String>, data: &Value) -> Self {
Self {
api,
event_id,
user_id: data
.get("user_id")
.and_then(|v| v.as_str())
.map(String::from),
channel_id: data
.get("channel_id")
.and_then(|v| v.as_str())
.map(String::from),
guild_id: data
.get("guild_id")
.and_then(|v| v.as_str())
.map(String::from),
emoji: Emoji::new(
data.get("emoji")
.unwrap_or(&Value::Object(serde_json::Map::new())),
),
target: ReactionTarget::new(
data.get("target")
.unwrap_or(&Value::Object(serde_json::Map::new())),
),
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
pub fn is_message_reaction(&self) -> bool {
matches!(self.target.target_type, Some(ReactionTargetType::Message))
}
pub fn is_post_reaction(&self) -> bool {
matches!(self.target.target_type, Some(ReactionTargetType::Post))
}
pub fn is_comment_reaction(&self) -> bool {
matches!(self.target.target_type, Some(ReactionTargetType::Comment))
}
pub fn is_reply_reaction(&self) -> bool {
matches!(self.target.target_type, Some(ReactionTargetType::Reply))
}
pub fn target_id(&self) -> Option<&str> {
self.target.id.as_deref()
}
pub fn emoji_id(&self) -> Option<&str> {
self.emoji.id.as_deref()
}
}
impl std::fmt::Display for Reaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Reaction {{ user_id: {:?}, channel_id: {:?}, guild_id: {:?}, target_type: {:?}, event_id: {:?} }}",
self.user_id, self.channel_id, self.guild_id, self.target.target_type, self.event_id
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReactionUser {
pub id: Option<String>,
pub username: Option<String>,
pub avatar: Option<String>,
}
impl ReactionUser {
pub fn new(data: &Value) -> Self {
Self {
id: data.get("id").and_then(|v| v.as_str()).map(String::from),
username: data
.get("username")
.and_then(|v| v.as_str())
.map(String::from),
avatar: data
.get("avatar")
.and_then(|v| v.as_str())
.map(String::from),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReactionUsers {
pub users: Vec<ReactionUser>,
pub cookie: Option<String>,
pub is_end: bool,
}
impl ReactionUsers {
pub fn new(data: &Value) -> Self {
let users = data
.get("users")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().map(ReactionUser::new).collect())
.unwrap_or_default();
Self {
users,
cookie: data
.get("cookie")
.and_then(|v| v.as_str())
.map(String::from),
is_end: data.get("is_end").and_then(|v| v.as_bool()).unwrap_or(true),
}
}
pub fn has_more_pages(&self) -> bool {
!self.is_end
}
pub fn user_count(&self) -> usize {
self.users.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reaction_target_type() {
assert_eq!(ReactionTargetType::Message as u8, 0);
assert_eq!(ReactionTargetType::Post as u8, 1);
assert_eq!(ReactionTargetType::Comment as u8, 2);
assert_eq!(ReactionTargetType::Reply as u8, 3);
}
#[test]
fn test_reaction_target_type_from() {
assert_eq!(ReactionTargetType::from(0), ReactionTargetType::Message);
assert_eq!(ReactionTargetType::from(1), ReactionTargetType::Post);
assert_eq!(ReactionTargetType::from(2), ReactionTargetType::Comment);
assert_eq!(ReactionTargetType::from(3), ReactionTargetType::Reply);
assert_eq!(ReactionTargetType::from(99), ReactionTargetType::Message); }
#[test]
fn test_emoji_creation() {
let data = serde_json::json!({
"id": "emoji123",
"type": 1
});
let emoji = Emoji::new(&data);
assert_eq!(emoji.id, Some("emoji123".to_string()));
assert_eq!(emoji.emoji_type, Some(1));
}
#[test]
fn test_reaction_target_creation() {
let data = serde_json::json!({
"id": "target123",
"type": 0
});
let target = ReactionTarget::new(&data);
assert_eq!(target.id, Some("target123".to_string()));
assert_eq!(target.target_type, Some(ReactionTargetType::Message));
}
#[test]
fn test_reaction_user_creation() {
let data = serde_json::json!({
"id": "user123",
"username": "testuser",
"avatar": "https://example.com/avatar.png"
});
let user = ReactionUser::new(&data);
assert_eq!(user.id, Some("user123".to_string()));
assert_eq!(user.username, Some("testuser".to_string()));
assert_eq!(
user.avatar,
Some("https://example.com/avatar.png".to_string())
);
}
}