twitch_highway 0.3.0

Twitch API
Documentation
use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize};

use crate::{
    serde_helpers::deserialize_optional_datetime,
    types::{BlockedTermId, BroadcasterId, ModeratorId, UnbanRequestId, UserId},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoModStatus {
    pub msg_id: String,
    pub is_permitted: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoModSetting {
    pub broadcaster_id: BroadcasterId,
    pub moderator_id: ModeratorId,
    pub overall_level: Option<u64>,
    pub disability: u64,
    pub aggression: u64,
    pub sexuality_sex_or_gender: u64,
    pub misogyny: u64,
    pub bullying: u64,
    pub swearing: u64,
    pub race_ethnicity_or_religion: u64,
    pub sex_based_terms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BannedUser {
    pub user_id: UserId,
    pub user_login: String,
    pub user_name: String,
    #[serde(default, deserialize_with = "deserialize_optional_datetime")]
    pub expires_at: Option<DateTime<FixedOffset>>,
    pub created_at: DateTime<FixedOffset>,
    pub reason: String,
    pub moderator_id: ModeratorId,
    pub moderator_login: String,
    pub moderator_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BanUser {
    pub broadcaster_id: BroadcasterId,
    pub moderator_id: ModeratorId,
    pub user_id: UserId,
    pub created_at: DateTime<FixedOffset>,
    pub end_time: Option<DateTime<FixedOffset>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UnbanRequestStatus {
    Pending,
    Approved,
    Denied,
    Acknowledged,
    Canceled,
}

impl UnbanRequestStatus {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Pending => "pending",
            Self::Approved => "approved",
            Self::Denied => "denied",
            Self::Acknowledged => "acknowledged",
            Self::Canceled => "canceled",
        }
    }
}

impl AsRef<str> for UnbanRequestStatus {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnbanRequest {
    pub id: UnbanRequestId,
    pub broadcaster_name: String,
    pub broadcaster_login: String,
    pub broadcaster_id: BroadcasterId,
    pub moderator_id: ModeratorId,
    pub moderator_login: String,
    pub moderator_name: String,
    pub user_id: UserId,
    pub user_login: String,
    pub user_name: String,
    pub text: String,
    pub status: UnbanRequestStatus,
    pub created_at: String,
    pub resolved_at: Option<String>,
    pub resolution_text: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockedTerm {
    pub broadcaster_id: BroadcasterId,
    pub moderator_id: ModeratorId,
    pub id: BlockedTermId,
    pub text: String,
    pub created_at: String,
    pub updated_at: String,
    pub expires_at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModeratedChannel {
    pub broadcaster_id: BroadcasterId,
    pub broadcaster_login: String,
    pub broadcaster_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Moderator {
    pub user_id: UserId,
    pub user_login: String,
    pub user_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShieldModeStatus {
    pub is_active: bool,
    pub moderator_id: ModeratorId,
    pub moderator_name: String,
    pub moderator_login: String,
    pub last_activated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WarnChatUser {
    pub broadcaster_id: BroadcasterId,
    pub user_id: UserId,
    pub moderator_id: ModeratorId,
    pub reason: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct CheckAutoMod {
    msg_id: String,
    msg_text: String,
}

impl CheckAutoMod {
    pub fn new(msg_id: impl Into<String>, msg_text: impl Into<String>) -> Self {
        Self {
            msg_id: msg_id.into(),
            msg_text: msg_text.into(),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AutoModAction {
    ALLOW,
    DENY,
}

#[derive(Serialize)]
pub(crate) struct CheckAutomodStatusBody<'a> {
    pub data: &'a [CheckAutoMod],
}

#[derive(Serialize)]
pub(crate) struct ManageHeldAutomodMessagesBody<'a> {
    pub user_id: &'a UserId,
    pub msg_id: &'a str,
    pub action: AutoModAction,
}

#[derive(Serialize)]
pub(crate) struct AddBlockedTermBody<'a> {
    pub text: &'a str,
}

#[derive(Serialize)]
pub(crate) struct UpdateShieldModeStatusBody {
    pub is_active: bool,
}

#[derive(Serialize)]
pub(crate) struct WarnChatUserBodyWrapper<'a> {
    pub data: WarnChatUserBody<'a>,
}

#[derive(Serialize)]
pub(crate) struct WarnChatUserBody<'a> {
    pub user_id: &'a UserId,
    pub reason: &'a str,
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use crate::moderation::{BannedUser, UnbanRequestStatus};

    #[test]
    fn unban_request_status_enum() {
        let statuses = vec![
            (UnbanRequestStatus::Pending, "pending"),
            (UnbanRequestStatus::Approved, "approved"),
            (UnbanRequestStatus::Denied, "denied"),
            (UnbanRequestStatus::Acknowledged, "acknowledged"),
            (UnbanRequestStatus::Canceled, "canceled"),
        ];

        for (status, expected_str) in statuses {
            assert_eq!(status.as_str(), expected_str);
            assert_eq!(status.as_ref(), expected_str);

            let serialized = serde_json::to_string(&status).unwrap();
            assert_eq!(serialized, format!("\"{}\"", expected_str));

            let deserialized: UnbanRequestStatus = serde_json::from_str(&serialized).unwrap();
            assert_eq!(deserialized.as_str(), expected_str);
        }
    }

    #[test]
    fn banned_user() {
        let input = json!({
              "user_id": "78325877",
              "user_login": "fisherskateboard566",
              "user_name": "FisherSkateboard566",
              "expires_at": "",
              "created_at": "2024-12-22T04:49:22Z",
              "reason": "CLI ban",
              "moderator_id": "6549720",
              "moderator_login": "fishersteve14",
              "moderator_name": "FisherSteve14"
        });

        serde_json::from_value::<BannedUser>(input).unwrap();
    }
}