#![doc(alias = "mod")]
#![doc(alias = "chat_moderator_actions")]
#![allow(deprecated)]
use crate::{pubsub, types};
use serde_derive::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(into = "String", try_from = "String")]
pub struct ChatModeratorActions {
pub user_id: u32,
pub channel_id: u32,
}
impl_de_ser!(
ChatModeratorActions,
"chat_moderator_actions",
user_id,
channel_id
);
impl pubsub::Topic for ChatModeratorActions {
#[cfg(feature = "twitch_oauth2")]
const SCOPE: twitch_oauth2::Validator =
twitch_oauth2::validator![twitch_oauth2::Scope::ChannelModerate];
fn into_topic(self) -> pubsub::Topics { super::Topics::ChatModeratorActions(self) }
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ModerationAction {
#[serde(deserialize_with = "pubsub::deserialize_default_from_null")]
pub args: Vec<String>,
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub created_by: Option<types::UserName>,
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub created_by_user_id: Option<types::UserId>,
#[serde(default)]
pub from_automod: bool,
pub moderation_action: ModerationActionCommand,
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub msg_id: Option<types::MsgId>,
pub target_user_id: types::UserId,
#[serde(rename = "type")]
pub type_: ModerationType,
#[doc(hidden)]
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub target_user_login: Option<String>,
#[doc(hidden)]
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub created_at: Option<types::Timestamp>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ModeratorAdded {
pub channel_id: types::UserId,
pub target_user_id: types::UserId,
pub moderation_action: ModerationActionCommand,
pub target_user_login: types::UserName,
pub created_by_user_id: types::UserId,
pub created_by: types::UserName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ModeratorRemoved {
pub channel_id: types::UserId,
pub target_user_id: types::UserId,
pub moderation_action: ModerationActionCommand,
pub target_user_login: types::UserName,
pub created_by_user_id: types::UserId,
pub created_by: types::UserName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ChannelTermsAction {
pub channel_id: types::UserId,
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub expires_at: Option<types::Timestamp>,
pub from_automod: bool,
pub id: types::BlockedTermId,
pub requester_id: types::UserId,
pub requester_login: types::UserName,
pub text: String,
#[serde(rename = "type")]
pub type_: ChannelAction,
#[serde(
default,
deserialize_with = "pubsub::deserialize_none_from_empty_string"
)]
pub updated_at: Option<types::Timestamp>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(tag = "type", content = "data")]
#[non_exhaustive]
pub enum ChatModeratorActionsReply {
#[serde(rename = "moderation_action")]
ModerationAction(ModerationAction),
#[serde(rename = "channel_terms_action")]
ChannelTermsAction(ChannelTermsAction),
#[serde(rename = "moderator_added")]
ModeratorAdded(ModeratorAdded),
#[serde(rename = "moderator_removed")]
ModeratorRemoved(ModeratorRemoved),
#[serde(rename = "deny_unban_request")]
DenyUnbanRequest(UnbanRequest),
#[serde(rename = "approve_unban_request")]
ApproveUnbanRequest(UnbanRequest),
#[serde(rename = "vip_added")]
VipAdded(VipAdded),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
pub struct VipAdded {
pub channel_id: types::UserId,
pub created_by: types::UserName,
pub created_by_user_id: types::UserId,
pub target_user_id: types::UserId,
pub target_user_login: types::UserName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct UnbanRequest {
pub created_by_id: types::UserId,
pub created_by_login: types::UserName,
pub moderation_action: ModerationActionCommand,
pub moderator_message: String,
pub target_user_id: types::UserId,
pub target_user_login: types::UserName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ModerationActionCommand {
Delete,
Timeout,
Untimeout,
Mod,
Unmod,
ModifiedAutomodProperties,
Ban,
Unban,
AutomodRejected,
ApproveAutomodMessage,
DeniedAutomodMessage,
Raid,
Unraid,
Slow,
#[serde(rename = "slowoff")]
SlowOff,
Followers,
#[serde(rename = "followersoff")]
FollowersOff,
Subscribers,
#[serde(rename = "subscribersoff")]
SubscribersOff,
#[serde(rename = "emoteonly")]
EmoteOnly,
#[serde(rename = "emoteonlyoff")]
EmoteOnlyOff,
Clear,
#[serde(rename = "r9kbeta")]
R9KBeta,
#[serde(rename = "r9kbetaoff")]
R9KBetaOff,
Vip,
Unvip,
Host,
Unhost,
#[serde(rename = "APPROVE_UNBAN_REQUEST")]
ApproveUnbanRequest,
#[serde(rename = "DENY_UNBAN_REQUEST")]
DenyUnbanRequest,
DeleteNotification,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ChannelAction {
AddPermittedTerm,
DeletePermittedTerm,
AddBlockedTerm,
DeleteBlockedTerm,
}
impl std::fmt::Display for ModerationActionCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use serde::Serialize;
self.serialize(f)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ModerationType {
ChatLoginModeration,
ChatChannelModeration,
ChatTargetedLoginModeration,
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::super::{Response, TopicData};
use super::*;
#[test]
fn mod_action_delete() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_login_moderation\",\"moderation_action\":\"delete\",\"args\":[\"tmo\",\"bop\",\"e513c02d-dca5-4480-9af5-e6078d954e42\"],\"created_by\":\"emilgardis\",\"created_by_user_id\":\"27620241\",\"msg_id\":\"\",\"target_user_id\":\"1234\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn check_deser() {
use std::convert::TryInto as _;
let s = "chat_moderator_actions.1337.1234";
assert_eq!(
ChatModeratorActions {
user_id: 1337,
channel_id: 1234,
},
s.to_string().try_into().unwrap()
);
}
#[test]
fn check_ser() {
let s = "chat_moderator_actions.1337.1234";
let right: String = ChatModeratorActions {
user_id: 1337,
channel_id: 1234,
}
.into();
assert_eq!(s.to_string(), right);
}
#[test]
fn mod_action_timeout() {
let source = r#"{"type":"MESSAGE","data":{"topic":"chat_moderator_actions.27620241.27620241","message":"{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_login_moderation\",\"moderation_action\":\"timeout\",\"args\":[\"tmo\",\"1\",\"\"],\"created_by\":\"emilgardis\",\"created_by_user_id\":\"27620241\",\"msg_id\":\"\",\"target_user_id\":\"1234\",\"target_user_login\":\"\",\"from_automod\":false}}"}}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_add_moderator() {
let source = r#"{"type":"MESSAGE","data":{"topic":"chat_moderator_actions.27620241.27620241","message":"{\"type\":\"moderator_added\", \"data\":{\"channel_id\":\"27620241\",\"target_user_id\":\"19264788\",\"moderation_action\":\"mod\",\"target_user_login\":\"nightbot\",\"created_by_user_id\":\"27620241\",\"created_by\":\"emilgardis\"}}"}}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_add_moderator_no_user_id() {
let source = r#"{"type":"MESSAGE","data":{"topic":"chat_moderator_actions.27620241.27620241","message":"{\"type\":\"moderator_added\", \"data\":{\"channel_id\":\"27620241\",\"target_user_id\":\"19264788\",\"moderation_action\":\"mod\",\"target_user_login\":\"nightbot\",\"created_by_user_id\":\"27620241\",\"created_by\":\"emilgardis\"}}"}}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_remove_moderator() {
let source = r#"{"type":"MESSAGE","data":{"topic":"chat_moderator_actions.691109305.129546453","message":"{\"type\":\"moderator_removed\",\"data\":{\"channel_id\":\"129546453\",\"target_user_id\":\"691109305\",\"moderation_action\":\"unmod\",\"target_user_login\":\"rewardmore\",\"created_by_user_id\":\"129546453\",\"created_by\":\"nerixyz\"}}"}}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_targeted_delete() {
let source = r#"{"type":"MESSAGE","data":{"topic":"chat_moderator_actions.27620241.80525799","message":"{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_targeted_login_moderation\",\"moderation_action\":\"delete_notification\",\"args\":[\"you have the moonpool no?\"],\"msg_id\":\"b7ffbf8a-ca9f-497e-bc6f-ae0e606e99dc\",\"target_user_id\":\"27620241\",\"target_user_login\":\"emilgardis\"}}"}}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_automod() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_channel_moderation\",\"moderation_action\":\"modified_automod_properties\",\"args\":null,\"created_by\":\"emilgardis\",\"created_by_user_id\":\"27620241\",\"msg_id\":\"\",\"target_user_id\":\"\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_automod_delete_blocked_term() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"channel_terms_action\",\"data\":{\"type\":\"delete_blocked_term\",\"id\":\"41a8f582-4c60-4ca1-aa10-91ec06161118\",\"text\":\"Hype\",\"requester_id\":\"27620241\",\"requester_login\":\"emilgardis\",\"channel_id\":\"27620241\",\"expires_at\":\"\",\"updated_at\":\"2021-05-10T21:35:28.745222679Z\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn mod_slowmode() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_channel_moderation\",\"moderation_action\":\"slow\",\"args\":[\"5\"],\"created_by\":\"tmo\",\"created_by_user_id\":\"1234\",\"msg_id\":\"\",\"target_user_id\":\"\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
#[cfg(not(feature = "deny_unknown_fields"))]
fn allow_unknown() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_channel_moderation\",\"moderation_action\":\"slow\",\"unknownfield\": 1,\"args\":[\"5\"],\"created_by\":\"tmo\",\"created_by_user_id\":\"1234\",\"msg_id\":\"\",\"target_user_id\":\"\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn deny_unban_request() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.80525799.80525799",
"message": "{\"type\":\"deny_unban_request\",\"data\":{\"moderation_action\":\"DENY_UNBAN_REQUEST\",\"created_by_id\":\"27620241\",\"created_by_login\":\"emilgardis\",\"moderator_message\":\"ok\",\"target_user_id\":\"465894629\",\"target_user_login\":\"emil_the_impostor\"}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn approve_unban_request() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.80525799.80525799",
"message": "{\"type\":\"approve_unban_request\",\"data\":{\"moderation_action\":\"APPROVE_UNBAN_REQUEST\",\"created_by_id\":\"27620241\",\"created_by_login\":\"emilgardis\",\"moderator_message\":\"ok\",\"target_user_id\":\"465894629\",\"target_user_login\":\"emil_the_impostor\"}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn vip_added() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.80525799.80525799",
"message": "{\"type\":\"vip_added\",\"data\":{\"channel_id\":\"80525799\",\"target_user_id\":\"56345511\",\"target_user_login\":\"bossquest\",\"created_by_user_id\":\"80525799\",\"created_by\":\"sessis\"}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn vip_added_mod_action() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.691109305.120183018",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_login_moderation\",\"moderation_action\":\"vip\",\"args\":[\"Floikka\"],\"created_by\":\"nam______________________\",\"created_by_user_id\":\"120183018\",\"created_at\":\"2022-12-20T16:41:26.168122804Z\",\"msg_id\":\"\",\"target_user_id\":\"85262774\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn vip_removed() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_login_moderation\",\"moderation_action\":\"unvip\",\"args\":[\"emil_the_impostor\"],\"created_by\":\"emilgardis\",\"created_by_user_id\":\"27620241\",\"created_at\":\"2021-07-27T22:28:31.075027599Z\",\"msg_id\":\"\",\"target_user_id\":\"465894629\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
#[test]
fn unraid() {
let source = r#"
{
"type": "MESSAGE",
"data": {
"topic": "chat_moderator_actions.27620241.27620241",
"message": "{\"type\":\"moderation_action\",\"data\":{\"type\":\"chat_channel_moderation\",\"moderation_action\":\"unraid\",\"args\":[\"emilgradis\"],\"created_by\":\"emilgardis\",\"created_by_user_id\":\"27620241\",\"created_at\":\"\",\"msg_id\":\"\",\"target_user_id\":\"\",\"target_user_login\":\"\",\"from_automod\":false}}"
}
}"#;
let actual = dbg!(Response::parse(source).unwrap());
assert!(matches!(
actual,
Response::Message {
data: TopicData::ChatModeratorActions { .. },
}
));
}
}