#![doc(alias = "channel.moderate")]
use super::*;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ChannelModerateV1 {
#[cfg_attr(feature = "typed-builder", builder(setter(into)))]
pub broadcaster_user_id: types::UserId,
#[cfg_attr(feature = "typed-builder", builder(setter(into)))]
pub moderator_user_id: types::UserId,
}
impl ChannelModerateV1 {
pub fn new(
broadcaster_user_id: impl Into<types::UserId>,
moderator_user_id: impl Into<types::UserId>,
) -> Self {
Self {
broadcaster_user_id: broadcaster_user_id.into(),
moderator_user_id: moderator_user_id.into(),
}
}
}
impl EventSubscription for ChannelModerateV1 {
type Payload = ChannelModerateV1Payload;
const EVENT_TYPE: EventType = EventType::ChannelModerate;
#[cfg(feature = "twitch_oauth2")]
const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![all(
any(
twitch_oauth2::Scope::ModeratorReadBlockedTerms,
twitch_oauth2::Scope::ModeratorManageBlockedTerms
),
any(
twitch_oauth2::Scope::ModeratorReadChatSettings,
twitch_oauth2::Scope::ModeratorManageChatSettings
),
any(
twitch_oauth2::Scope::ModeratorReadUnbanRequests,
twitch_oauth2::Scope::ModeratorManageUnbanRequests
),
any(
twitch_oauth2::Scope::ModeratorReadBannedUsers,
twitch_oauth2::Scope::ModeratorManageBannedUsers
),
any(
twitch_oauth2::Scope::ModeratorReadChatMessages,
twitch_oauth2::Scope::ModeratorManageChatMessages
),
twitch_oauth2::Scope::ModeratorReadModerators,
twitch_oauth2::Scope::ModeratorReadVips,
)];
const VERSION: &'static str = "1";
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ChannelModerateV1Payload {
pub broadcaster_user_id: types::UserId,
pub broadcaster_user_login: types::UserName,
pub broadcaster_user_name: types::DisplayName,
pub source_broadcaster_user_id: Option<types::UserId>,
pub source_broadcaster_user_login: Option<types::UserName>,
pub source_broadcaster_user_name: Option<types::DisplayName>,
pub moderator_user_id: types::UserId,
pub moderator_user_name: types::DisplayName,
pub moderator_user_login: types::UserName,
#[serde(flatten)]
pub action: ActionV1,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum ActionV1 {
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Followers(Followers),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Slow(Slow),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Vip(Vip),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unvip(Unvip),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Mod(Mod),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unmod(Unmod),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Ban(Ban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unban(Unban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Timeout(Timeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Untimeout(Untimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Raid(Raid),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unraid(Unraid),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Delete(Delete),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
ApproveUnbanRequest(UnbanRequest),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
DenyUnbanRequest(UnbanRequest),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatBan(SharedChatBan),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatUnban(SharedChatUnban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatTimeout(SharedChatTimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatUntimeout(SharedChatUntimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatDelete(SharedChatDelete),
#[serde(rename = "emoteonly")]
EmoteOnly,
#[serde(rename = "emoteonlyoff")]
EmoteOnlyOff,
#[serde(rename = "followersoff")]
FollowersOff,
Uniquechat,
#[serde(rename = "uniquechatoff")]
UniquechatOff,
#[serde(rename = "slowoff")]
SlowOff,
Subscribers,
SubscribersOff,
#[serde(with = "crate::eventsub::enum_field_as_inner")]
AddBlockedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
AddPermittedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
RemoveBlockedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
RemovePermittedTerm(AutomodTerms),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Followers {
pub follow_duration_minutes: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Slow {
pub wait_time_seconds: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Vip {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Unvip {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Mod {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Unmod {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Ban {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
#[serde(
default,
deserialize_with = "crate::deserialize_none_from_empty_string"
)]
pub reason: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Unban {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Timeout {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
#[serde(
default,
deserialize_with = "crate::deserialize_none_from_empty_string"
)]
pub reason: Option<String>,
pub expires_at: types::Timestamp,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Untimeout {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Raid {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
pub viewer_count: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Unraid {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Delete {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
pub message_id: types::MsgId,
pub message_body: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct AutomodTerms {
pub action: AutomodTermAction,
pub list: AutomodTermList,
pub terms: Vec<String>,
pub from_automod: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum AutomodTermAction {
Add,
Remove,
#[serde(untagged)]
Unknown(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum AutomodTermList {
Blocked,
Permitted,
#[serde(untagged)]
Unknown(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct UnbanRequest {
pub is_approved: bool,
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
pub moderator_message: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SharedChatBan(pub Ban);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SharedChatUnban(pub Unban);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SharedChatTimeout(pub Timeout);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SharedChatUntimeout(pub Untimeout);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[serde(transparent)]
#[repr(transparent)]
pub struct SharedChatDelete(pub Delete);
macro_rules! named_fields {
($($typ:ty = $name:literal),*) => {
$(
impl crate::eventsub::NamedField for $typ {
const NAME: &'static str = $name;
}
)*
};
}
named_fields! {
Followers = "followers",
Slow = "slow",
Vip = "vip",
Unvip = "unvip",
Mod = "mod",
Unmod = "unmod",
Ban = "ban",
Unban = "unban",
Timeout = "timeout",
Untimeout = "untimeout",
Raid = "raid",
Unraid = "unraid",
Delete = "delete",
AutomodTerms = "automod_terms",
UnbanRequest = "unban_request",
SharedChatBan = "shared_chat_ban",
SharedChatUnban = "shared_chat_unban",
SharedChatTimeout = "shared_chat_timeout",
SharedChatUntimeout = "shared_chat_untimeout",
SharedChatDelete = "shared_chat_delete"
}
#[cfg(test)]
#[test]
fn parse_payload_v1_timeout() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "53be57fd-baa0-470c-af30-f8f0958f7f0b",
"status": "enabled",
"type": "channel.moderate",
"version": "1",
"condition": {
"broadcaster_user_id": "129546453",
"moderator_user_id": "129546453"
},
"transport": {
"method": "websocket",
"session_id": "AgoQ2sjLsebwRk2kuZ5zFe2CFRIGY2VsbC1j"
},
"created_at": "2024-11-27T18:12:03.396116773Z",
"cost": 0
},
"event": {
"broadcaster_user_id": "129546453",
"broadcaster_user_login": "nerixyz",
"broadcaster_user_name": "nerixyz",
"source_broadcaster_user_id": null,
"source_broadcaster_user_login": null,
"source_broadcaster_user_name": null,
"moderator_user_id": "129546453",
"moderator_user_login": "nerixyz",
"moderator_user_name": "nerixyz",
"action": "timeout",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"mod": null,
"unmod": null,
"ban": null,
"unban": null,
"timeout": {
"user_id": "141981764",
"user_login": "twitchdev",
"user_name": "TwitchDev",
"reason": "test Kappa",
"expires_at": "2024-11-27T18:12:43.640505703Z"
},
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV1(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "129546453");
assert_eq!(notif.source_broadcaster_user_id, None);
assert_eq!(notif.moderator_user_id.as_str(), "129546453");
let ActionV1::Timeout(action) = notif.action else {
panic!("invalid action");
};
assert_eq!(action.user_id.as_str(), "141981764");
}
#[cfg(test)]
#[test]
fn parse_payload_v1_emoteonly() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "1",
"cost": 0,
"condition": {
"broadcaster_user_id": "1337",
"moderator_user_id": "1337"
},
"transport": {
"method": "webhook",
"callback": "https://example.com/webhooks/callback"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
},
"event": {
"broadcaster_user_id": "1337",
"broadcaster_user_login": "glowillig",
"broadcaster_user_name": "glowillig",
"moderator_user_id": "424596340",
"moderator_user_login": "quotrok",
"moderator_user_name": "quotrok",
"action": "emoteonly",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"mod": null,
"unmod": null,
"ban": null,
"unban": null,
"timeout": null,
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV1(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "1337");
assert_eq!(notif.source_broadcaster_user_id, None);
assert_eq!(notif.moderator_user_id.as_str(), "424596340");
assert_eq!(notif.action, ActionV1::EmoteOnly);
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typed-builder", derive(typed_builder::TypedBuilder))]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct ChannelModerateV2 {
#[cfg_attr(feature = "typed-builder", builder(setter(into)))]
pub broadcaster_user_id: types::UserId,
#[cfg_attr(feature = "typed-builder", builder(setter(into)))]
pub moderator_user_id: types::UserId,
}
impl ChannelModerateV2 {
pub fn new(
broadcaster_user_id: impl Into<types::UserId>,
moderator_user_id: impl Into<types::UserId>,
) -> Self {
Self {
broadcaster_user_id: broadcaster_user_id.into(),
moderator_user_id: moderator_user_id.into(),
}
}
}
impl EventSubscription for ChannelModerateV2 {
type Payload = ChannelModerateV2Payload;
const EVENT_TYPE: EventType = EventType::ChannelModerate;
#[cfg(feature = "twitch_oauth2")]
const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![all(
any(
twitch_oauth2::Scope::ModeratorReadBlockedTerms,
twitch_oauth2::Scope::ModeratorManageBlockedTerms
),
any(
twitch_oauth2::Scope::ModeratorReadChatSettings,
twitch_oauth2::Scope::ModeratorManageChatSettings
),
any(
twitch_oauth2::Scope::ModeratorReadUnbanRequests,
twitch_oauth2::Scope::ModeratorManageUnbanRequests
),
any(
twitch_oauth2::Scope::ModeratorReadBannedUsers,
twitch_oauth2::Scope::ModeratorManageBannedUsers
),
any(
twitch_oauth2::Scope::ModeratorReadChatMessages,
twitch_oauth2::Scope::ModeratorManageChatMessages
),
twitch_oauth2::Scope::ModeratorReadModerators,
twitch_oauth2::Scope::ModeratorReadVips,
any(
twitch_oauth2::Scope::ModeratorReadWarnings,
twitch_oauth2::Scope::ModeratorManageWarnings
),
)];
const VERSION: &'static str = "2";
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ChannelModerateV2Payload {
pub broadcaster_user_id: types::UserId,
pub broadcaster_user_login: types::UserName,
pub broadcaster_user_name: types::DisplayName,
pub source_broadcaster_user_id: Option<types::UserId>,
pub source_broadcaster_user_login: Option<types::UserName>,
pub source_broadcaster_user_name: Option<types::DisplayName>,
pub moderator_user_id: types::UserId,
pub moderator_user_name: types::DisplayName,
pub moderator_user_login: types::UserName,
#[serde(flatten)]
pub action: ActionV2,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum ActionV2 {
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Followers(Followers),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Slow(Slow),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Vip(Vip),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unvip(Unvip),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Mod(Mod),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unmod(Unmod),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Ban(Ban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unban(Unban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Timeout(Timeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Untimeout(Untimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Raid(Raid),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Unraid(Unraid),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Delete(Delete),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
ApproveUnbanRequest(UnbanRequest),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
DenyUnbanRequest(UnbanRequest),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatBan(SharedChatBan),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatUnban(SharedChatUnban),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatTimeout(SharedChatTimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatUntimeout(SharedChatUntimeout),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
SharedChatDelete(SharedChatDelete),
#[serde(rename = "emoteonly")]
EmoteOnly,
#[serde(rename = "emoteonlyoff")]
EmoteOnlyOff,
#[serde(rename = "followersoff")]
FollowersOff,
Uniquechat,
#[serde(rename = "uniquechatoff")]
UniquechatOff,
#[serde(rename = "slowoff")]
SlowOff,
Subscribers,
SubscribersOff,
#[serde(with = "crate::eventsub::enum_field_as_inner")]
AddBlockedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
AddPermittedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
RemoveBlockedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
RemovePermittedTerm(AutomodTerms),
#[serde(with = "crate::eventsub::enum_field_as_inner")]
Warn(Warn),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
#[non_exhaustive]
pub struct Warn {
pub user_id: types::UserId,
pub user_login: types::UserName,
pub user_name: types::DisplayName,
#[serde(
default,
deserialize_with = "crate::deserialize_none_from_empty_string"
)]
pub reason: Option<String>,
pub chat_rules_cited: Option<Vec<String>>,
}
named_fields! { Warn = "warn" }
#[cfg(test)]
#[test]
fn parse_payload_v2_timeout() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "5a893cd8-c10e-4198-a620-e9f35fd6ccd6",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"condition": {
"broadcaster_user_id": "129546453",
"moderator_user_id": "129546453"
},
"transport": {
"method": "websocket",
"session_id": "AgoQMmDnfVEhRI6iLiBniEWHkxIGY2VsbC1j"
},
"created_at": "2024-11-27T20:01:50.171283653Z",
"cost": 0
},
"event": {
"broadcaster_user_id": "129546453",
"broadcaster_user_login": "nerixyz",
"broadcaster_user_name": "nerixyz",
"source_broadcaster_user_id": null,
"source_broadcaster_user_login": null,
"source_broadcaster_user_name": null,
"moderator_user_id": "129546453",
"moderator_user_login": "nerixyz",
"moderator_user_name": "nerixyz",
"action": "timeout",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"mod": null,
"unmod": null,
"ban": null,
"unban": null,
"timeout": {
"user_id": "141981764",
"user_login": "twitchdev",
"user_name": "TwitchDev",
"reason": "test Kappa",
"expires_at": "2024-11-27T20:01:55.358149527Z"
},
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"warn": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV2(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "129546453");
assert_eq!(notif.source_broadcaster_user_id, None);
assert_eq!(notif.moderator_user_id.as_str(), "129546453");
let ActionV2::Timeout(action) = notif.action else {
panic!("invalid action");
};
assert_eq!(action.user_id.as_str(), "141981764");
}
#[cfg(test)]
#[test]
fn parse_payload_v2_warn() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"cost": 0,
"condition": {
"broadcaster_user_id": "423374343",
"moderator_user_id": "424596340"
},
"transport": {
"method": "webhook",
"callback": "https://example.com/webhooks/callback"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
},
"event": {
"broadcaster_user_id": "423374343",
"broadcaster_user_login": "glowillig",
"broadcaster_user_name": "glowillig",
"source_broadcaster_user_id": "41292030",
"source_broadcaster_user_login": "adflynn404",
"source_broadcaster_user_name": "adflynn404",
"moderator_user_id": "424596340",
"moderator_user_login": "quotrok",
"moderator_user_name": "quotrok",
"action": "warn",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"warn": {
"user_id": "141981764",
"user_login": "twitchdev",
"user_name": "TwitchDev",
"reason": "cut it out",
"chat_rules_cited": null
},
"unmod": null,
"ban": null,
"unban": null,
"timeout": null,
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV2(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "423374343");
assert_eq!(
notif.source_broadcaster_user_id.unwrap().as_str(),
"41292030"
);
assert_eq!(notif.moderator_user_id.as_str(), "424596340");
let ActionV2::Warn(action) = notif.action else {
panic!("invalid action");
};
assert_eq!(action.user_id.as_str(), "141981764");
assert_eq!(action.reason.unwrap(), "cut it out");
assert_eq!(action.chat_rules_cited, None);
}
#[cfg(test)]
#[test]
fn parse_payload_v2_shared_chat_timeout() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"cost": 0,
"condition": {
"broadcaster_user_id": "423374343",
"moderator_user_id": "424596340"
},
"transport": {
"method": "webhook",
"callback": "https://example.com/webhooks/callback"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
},
"event": {
"broadcaster_user_id": "423374343",
"broadcaster_user_login": "glowillig",
"broadcaster_user_name": "glowillig",
"source_broadcaster_user_id": "41292030",
"source_broadcaster_user_login": "adflynn404",
"source_broadcaster_user_name": "adflynn404",
"moderator_user_id": "424596340",
"moderator_user_login": "quotrok",
"moderator_user_name": "quotrok",
"action": "shared_chat_timeout",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"warn": null,
"unmod": null,
"ban": null,
"unban": null,
"timeout": null,
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": {
"user_id": "141981764",
"user_login": "twitchdev",
"user_name": "TwitchDev",
"reason": "Has never seen the Harry Potter films.",
"expires_at": "2022-03-15T02:00:28Z"
},
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV2(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "423374343");
assert_eq!(
notif.source_broadcaster_user_id.unwrap().as_str(),
"41292030"
);
assert_eq!(notif.moderator_user_id.as_str(), "424596340");
let ActionV2::SharedChatTimeout(SharedChatTimeout(action)) = notif.action else {
panic!("invalid action");
};
assert_eq!(action.user_id.as_str(), "141981764");
assert_eq!(
action.reason.unwrap(),
"Has never seen the Harry Potter films."
);
}
#[cfg(test)]
#[test]
fn parse_payload_v2_mod() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"cost": 0,
"condition": {
"broadcaster_user_id": "423374343",
"moderator_user_id": "423374343"
},
"transport": {
"method": "webhook",
"callback": "https://example.com/webhooks/callback"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
},
"event": {
"broadcaster_user_id": "423374343",
"broadcaster_user_login": "glowillig",
"broadcaster_user_name": "glowillig",
"moderator_user_id": "424596340",
"moderator_user_login": "quotrok",
"moderator_user_name": "quotrok",
"action": "mod",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"warn": null,
"mod": {
"user_id": "141981764",
"user_login": "twitchdev",
"user_name": "TwitchDev"
},
"unmod": null,
"ban": null,
"unban": null,
"timeout": null,
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV2(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "423374343");
assert_eq!(notif.source_broadcaster_user_id, None);
assert_eq!(notif.moderator_user_id.as_str(), "424596340");
let ActionV2::Mod(action) = notif.action else {
panic!("invalid action");
};
assert_eq!(action.user_id.as_str(), "141981764");
}
#[cfg(test)]
#[test]
fn parse_payload_v2_emoteonly() {
use crate::eventsub::{Event, Message};
let payload = r##"
{
"subscription": {
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"cost": 0,
"condition": {
"broadcaster_user_id": "423374343",
"moderator_user_id": "423374343"
},
"transport": {
"method": "webhook",
"callback": "https://example.com/webhooks/callback"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
},
"event": {
"broadcaster_user_id": "423374343",
"broadcaster_user_login": "glowillig",
"broadcaster_user_name": "glowillig",
"moderator_user_id": "424596340",
"moderator_user_login": "quotrok",
"moderator_user_name": "quotrok",
"action": "emoteonly",
"followers": null,
"slow": null,
"vip": null,
"unvip": null,
"mod": null,
"unmod": null,
"ban": null,
"unban": null,
"warn": null,
"timeout": null,
"untimeout": null,
"raid": null,
"unraid": null,
"delete": null,
"automod_terms": null,
"unban_request": null,
"shared_chat_ban": null,
"shared_chat_unban": null,
"shared_chat_timeout": null,
"shared_chat_untimeout": null,
"shared_chat_delete": null
}
}
"##;
let val = Event::parse(payload).unwrap();
crate::tests::roundtrip(&val);
let Event::ChannelModerateV2(val) = val else {
panic!("invalid event type");
};
let Message::Notification(notif) = val.message else {
panic!("invalid message type");
};
assert_eq!(notif.broadcaster_user_id.as_str(), "423374343");
assert_eq!(notif.source_broadcaster_user_id, None);
assert_eq!(notif.moderator_user_id.as_str(), "424596340");
assert_eq!(notif.action, ActionV2::EmoteOnly);
}