diff --git a/src/eventsub/automod/message/mod.rs b/src/eventsub/automod/message/mod.rs index 6b06624afb..bee712e4c7 100644 --- a/src/eventsub/automod/message/mod.rs +++ b/src/eventsub/automod/message/mod.rs @@ -5,6 +5,25 @@ use crate::types; use serde_derive::{Deserialize, Serialize}; pub mod hold; +pub mod update; #[doc(inline)] pub use hold::{AutomodMessageHoldV1, AutomodMessageHoldV1Payload}; +#[doc(inline)] +pub use update::{AutomodMessageUpdateV1, AutomodMessageUpdateV1Payload}; + +/// A message's Automod status +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[non_exhaustive] +#[serde(rename_all = "snake_case")] +pub enum AutomodMessageStatus { + /// The message was approved (shown in chat) + Approved, + /// The message was denied (not shown in chat) + Denied, + /// The message is too old, it can't be acted upon anymore. + Expired, + /// An unknown Automod message status, contains the raw string provided by Twitch. + #[serde(untagged)] + Unknown(String), +} diff --git a/src/eventsub/automod/message/update.rs b/src/eventsub/automod/message/update.rs new file mode 100644 index 0000000000..b5952f5f62 --- /dev/null +++ b/src/eventsub/automod/message/update.rs @@ -0,0 +1,150 @@ +#![doc(alias = "automod.message.update")] +//! a message in the automod queue had its status changed + +use super::*; +/// [`automod.message.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodmessageupdate): a message in the automod queue had its status changed. +#[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 AutomodMessageUpdateV1 { + /// User ID of the broadcaster (channel). Maximum: 1 + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub broadcaster_user_id: types::UserId, + /// User ID of the moderator. + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub moderator_user_id: types::UserId, +} + +impl AutomodMessageUpdateV1 { + /// Get automod update notifications for messages in this channel as a moderator + pub fn new( + broadcaster_user_id: impl Into, + moderator_user_id: impl Into, + ) -> Self { + Self { + broadcaster_user_id: broadcaster_user_id.into(), + moderator_user_id: moderator_user_id.into(), + } + } +} + +impl EventSubscription for AutomodMessageUpdateV1 { + type Payload = AutomodMessageUpdateV1Payload; + + const EVENT_TYPE: EventType = EventType::AutomodMessageUpdate; + #[cfg(feature = "twitch_oauth2")] + const SCOPE: twitch_oauth2::Validator = + twitch_oauth2::validator![twitch_oauth2::Scope::ModeratorManageAutoMod]; + const VERSION: &'static str = "1"; +} + +/// [`automod.message.update`](AutomodMessageUpdateV1) response payload. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] +#[non_exhaustive] +pub struct AutomodMessageUpdateV1Payload { + /// The ID of the broadcaster specified in the request. + pub broadcaster_user_id: types::UserId, + /// The login of the broadcaster specified in the request. + pub broadcaster_user_login: types::UserName, + /// The user name of the broadcaster specified in the request. + pub broadcaster_user_name: types::DisplayName, + + /// The message sender’s user ID. + pub user_id: types::UserId, + /// The message sender’s login name. + pub user_login: types::UserName, + /// The message sender’s display name. + pub user_name: types::DisplayName, + + /// The ID of the moderator. + pub moderator_user_id: types::UserId, + /// The login of the moderator. + pub moderator_user_login: types::UserName, + /// The moderator’s user name. + pub moderator_user_name: types::DisplayName, + + /// The ID of the message that was flagged by automod. + pub message_id: types::MsgId, + /// The body of the message. + pub message: crate::eventsub::channel::chat::Message, + /// The category of the message. + pub category: super::AutomodCategory, + /// The level of severity. Measured between 1 to 4. + pub level: u8, + /// The message’s status. + pub status: AutomodMessageStatus, + /// The timestamp of when automod saved the message. + pub held_at: types::Timestamp, +} + +#[cfg(test)] +#[test] +fn parse_payload() { + use crate::eventsub::{Event, Message}; + + let payload = r##" + { + "subscription": { + "id": "79cc58a2-1c34-48e0-97fe-126d5d77bf10", + "status": "enabled", + "type": "automod.message.update", + "version": "1", + "condition": { + "broadcaster_user_id": "129546453", + "moderator_user_id": "129546453" + }, + "transport": { + "method": "websocket", + "session_id": "AgoQZ12VWLotRG6u3pudLlbhvhIGY2VsbC1j" + }, + "created_at": "2024-11-03T11:52:04.695680375Z", + "cost": 0 + }, + "event": { + "broadcaster_user_id": "129546453", + "broadcaster_user_login": "nerixyz", + "broadcaster_user_name": "nerixyz", + "user_id": "489584266", + "user_login": "uint128", + "user_name": "uint128", + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "message_id": "8b722958-741f-4013-8a8b-c7793d3aef9f", + "message": { + "text": "boobs", + "fragments": [ + { + "type": "text", + "text": "boobs", + "cheermote": null, + "emote": null + } + ] + }, + "category": "sexwords", + "level": 4, + "status": "approved", + "held_at": "2024-11-03T11:53:45.331308397Z" + } + } + "##; + + let val = Event::parse(payload).unwrap(); + crate::tests::roundtrip(&val); + + let Event::AutomodMessageUpdateV1(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.category, AutomodCategory::Sexwords); + assert_eq!(notif.level, 4); + assert_eq!(notif.status, AutomodMessageStatus::Approved); + assert_eq!(notif.message.fragments.len(), 1); +} diff --git a/src/eventsub/automod/mod.rs b/src/eventsub/automod/mod.rs index 67fc365d38..aeaacec325 100644 --- a/src/eventsub/automod/mod.rs +++ b/src/eventsub/automod/mod.rs @@ -4,9 +4,19 @@ use super::{EventSubscription, EventType}; use serde_derive::{Deserialize, Serialize}; pub mod message; +pub mod settings; +pub mod terms; #[doc(inline)] pub use message::{AutomodMessageHoldV1, AutomodMessageHoldV1Payload}; +#[doc(inline)] +pub use message::{AutomodMessageUpdateV1, AutomodMessageUpdateV1Payload}; + +#[doc(inline)] +pub use terms::{AutomodTermsUpdateV1, AutomodTermsUpdateV1Payload}; + +#[doc(inline)] +pub use settings::{AutomodSettingsUpdateV1, AutomodSettingsUpdateV1Payload}; /// A category identified by automod for a message. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/eventsub/automod/settings/mod.rs b/src/eventsub/automod/settings/mod.rs new file mode 100644 index 0000000000..34e3ba43e2 --- /dev/null +++ b/src/eventsub/automod/settings/mod.rs @@ -0,0 +1,10 @@ +#![doc(alias = "automod.settings")] +//! Events for Automod settings +use super::{EventSubscription, EventType}; +use crate::types; +use serde_derive::{Deserialize, Serialize}; + +pub mod update; + +#[doc(inline)] +pub use update::{AutomodSettingsUpdateV1, AutomodSettingsUpdateV1Payload}; diff --git a/src/eventsub/automod/settings/update.rs b/src/eventsub/automod/settings/update.rs new file mode 100644 index 0000000000..487953d611 --- /dev/null +++ b/src/eventsub/automod/settings/update.rs @@ -0,0 +1,209 @@ +#![doc(alias = "automod.settings.update")] +//! a notification is sent when a broadcaster’s automod settings are updated. + +use super::*; +/// [`automod.settings.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodsettingsupdate): a notification is sent when a broadcaster’s automod settings are updated. +#[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 AutomodSettingsUpdateV1 { + /// User ID of the broadcaster (channel). Maximum: 1 + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub broadcaster_user_id: types::UserId, + /// User ID of the moderator. + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub moderator_user_id: types::UserId, +} + +impl AutomodSettingsUpdateV1 { + /// Get notifications for updates on Automod settings in this channel as a moderator + pub fn new( + broadcaster_user_id: impl Into, + moderator_user_id: impl Into, + ) -> Self { + Self { + broadcaster_user_id: broadcaster_user_id.into(), + moderator_user_id: moderator_user_id.into(), + } + } +} + +impl EventSubscription for AutomodSettingsUpdateV1 { + type Payload = AutomodSettingsUpdateV1Payload; + + const EVENT_TYPE: EventType = EventType::AutomodSettingsUpdate; + #[cfg(feature = "twitch_oauth2")] + const SCOPE: twitch_oauth2::Validator = + twitch_oauth2::validator![twitch_oauth2::Scope::ModeratorReadAutomodSettings]; + const VERSION: &'static str = "1"; +} + +/// [`automod.settings.update`](AutomodSettingsUpdateV1) response payload. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] +#[non_exhaustive] +pub struct AutomodSettingsUpdateV1Payload { + /// The ID of the broadcaster specified in the request. + pub broadcaster_user_id: types::UserId, + /// The login of the broadcaster specified in the request. + pub broadcaster_user_login: types::UserName, + /// The user name of the broadcaster specified in the request. + pub broadcaster_user_name: types::DisplayName, + + /// The ID of the moderator who changed the channel settings. + pub moderator_user_id: types::UserId, + /// The login of the moderator. + pub moderator_user_login: types::UserName, + /// The moderator’s user name. + pub moderator_user_name: types::DisplayName, + + /// The default AutoMod level for the broadcaster. This field is [None] if the broadcaster has set one or more of the individual settings. + pub overall_level: Option, + /// The Automod level for discrimination against disability. + pub disability: u8, + /// The Automod level for hostility involving aggression. + pub aggression: u8, + /// The AutoMod level for discrimination based on sexuality, sex, or gender. + pub sexuality_sex_or_gender: u8, + /// The Automod level for discrimination against women. + pub misogyny: u8, + /// The Automod level for hostility involving name calling or insults. + pub bullying: u8, + /// The Automod level for profanity. + pub swearing: u8, + /// The Automod level for racial discrimination. + pub race_ethnicity_or_religion: u8, + /// The Automod level for sexual content. + pub sex_based_terms: u8, +} + +#[cfg(test)] +#[test] +fn parse_payload_overall() { + use crate::eventsub::{Event, Message}; + + let payload = r##" + { + "subscription": { + "id": "ef82080b-fe22-4959-996a-f7a1ab5467ea", + "status": "enabled", + "type": "automod.settings.update", + "version": "1", + "condition": { + "broadcaster_user_id": "129546453", + "moderator_user_id": "129546453" + }, + "transport": { + "method": "websocket", + "session_id": "AgoQ_7uykM5qRQGvMF6kjH9xkhIGY2VsbC1j" + }, + "created_at": "2024-11-03T12:00:36.504376879Z", + "cost": 0 + }, + "event": { + "broadcaster_user_id": "129546453", + "broadcaster_user_name": "nerixyz", + "broadcaster_user_login": "nerixyz", + "moderator_user_id": "129546453", + "moderator_user_name": "nerixyz", + "moderator_user_login": "nerixyz", + "overall_level": 1, + "bullying": 0, + "disability": 0, + "race_ethnicity_or_religion": 1, + "misogyny": 0, + "sexuality_sex_or_gender": 1, + "aggression": 1, + "sex_based_terms": 0, + "swearing": 0 + } + } + "##; + + let val = Event::parse(payload).unwrap(); + crate::tests::roundtrip(&val); + + let Event::AutomodSettingsUpdateV1(val) = val else { + panic!("invalid event type"); + }; + let Message::Notification(notif) = val.message else { + panic!("invalid settings type"); + }; + + assert_eq!(notif.broadcaster_user_id.as_str(), "129546453"); + assert_eq!(notif.overall_level, Some(1)); + assert_eq!(notif.bullying, 0); + assert_eq!(notif.disability, 0); + assert_eq!(notif.race_ethnicity_or_religion, 1); + assert_eq!(notif.misogyny, 0); + assert_eq!(notif.sexuality_sex_or_gender, 1); + assert_eq!(notif.aggression, 1); + assert_eq!(notif.sex_based_terms, 0); + assert_eq!(notif.swearing, 0); +} + +#[cfg(test)] +#[test] +fn parse_payload_individual() { + use crate::eventsub::{Event, Message}; + + let payload = r##" + { + "subscription": { + "id": "ef82080b-fe22-4959-996a-f7a1ab5467ea", + "status": "enabled", + "type": "automod.settings.update", + "version": "1", + "condition": { + "broadcaster_user_id": "129546453", + "moderator_user_id": "129546453" + }, + "transport": { + "method": "websocket", + "session_id": "AgoQ_7uykM5qRQGvMF6kjH9xkhIGY2VsbC1j" + }, + "created_at": "2024-11-03T12:00:36.504376879Z", + "cost": 0 + }, + "event": { + "broadcaster_user_id": "129546453", + "broadcaster_user_name": "nerixyz", + "broadcaster_user_login": "nerixyz", + "moderator_user_id": "129546453", + "moderator_user_name": "nerixyz", + "moderator_user_login": "nerixyz", + "overall_level": null, + "bullying": 2, + "disability": 0, + "race_ethnicity_or_religion": 1, + "misogyny": 0, + "sexuality_sex_or_gender": 1, + "aggression": 1, + "sex_based_terms": 0, + "swearing": 0 + } + } + "##; + + let val = Event::parse(payload).unwrap(); + crate::tests::roundtrip(&val); + + let Event::AutomodSettingsUpdateV1(val) = val else { + panic!("invalid event type"); + }; + let Message::Notification(notif) = val.message else { + panic!("invalid settings type"); + }; + + assert_eq!(notif.broadcaster_user_id.as_str(), "129546453"); + assert_eq!(notif.overall_level, None); + assert_eq!(notif.bullying, 2); + assert_eq!(notif.disability, 0); + assert_eq!(notif.race_ethnicity_or_religion, 1); + assert_eq!(notif.misogyny, 0); + assert_eq!(notif.sexuality_sex_or_gender, 1); + assert_eq!(notif.aggression, 1); + assert_eq!(notif.sex_based_terms, 0); + assert_eq!(notif.swearing, 0); +} diff --git a/src/eventsub/automod/terms/mod.rs b/src/eventsub/automod/terms/mod.rs new file mode 100644 index 0000000000..38eae2ac1c --- /dev/null +++ b/src/eventsub/automod/terms/mod.rs @@ -0,0 +1,10 @@ +#![doc(alias = "automod.terms")] +//! Events for Automod terms (allowed/denied words) +use super::{EventSubscription, EventType}; +use crate::types; +use serde_derive::{Deserialize, Serialize}; + +pub mod update; + +#[doc(inline)] +pub use update::{AutomodTermAction, AutomodTermsUpdateV1, AutomodTermsUpdateV1Payload}; diff --git a/src/eventsub/automod/terms/update.rs b/src/eventsub/automod/terms/update.rs new file mode 100644 index 0000000000..a091beb0fd --- /dev/null +++ b/src/eventsub/automod/terms/update.rs @@ -0,0 +1,141 @@ +#![doc(alias = "automod.terms.update")] +//! a notification when a broadcaster’s automod terms are updated + +use super::*; +/// [`automod.terms.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodtermsupdate): A notification is sent when a broadcaster’s automod terms are updated. Changes to private terms are not sent. +#[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 AutomodTermsUpdateV1 { + /// User ID of the broadcaster (channel). Maximum: 1 + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub broadcaster_user_id: types::UserId, + /// User ID of the moderator creating the subscription. Maximum: 1 + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub moderator_user_id: types::UserId, +} + +impl AutomodTermsUpdateV1 { + /// Get automod update notifications for permitted/blocked terms in this channel as a moderator + pub fn new( + broadcaster_user_id: impl Into, + moderator_user_id: impl Into, + ) -> Self { + Self { + broadcaster_user_id: broadcaster_user_id.into(), + moderator_user_id: moderator_user_id.into(), + } + } +} + +impl EventSubscription for AutomodTermsUpdateV1 { + type Payload = AutomodTermsUpdateV1Payload; + + const EVENT_TYPE: EventType = EventType::AutomodTermsUpdate; + #[cfg(feature = "twitch_oauth2")] + const SCOPE: twitch_oauth2::Validator = + twitch_oauth2::validator![twitch_oauth2::Scope::ModeratorManageAutoMod]; + const VERSION: &'static str = "1"; +} + +/// [`automod.terms.update`](AutomodTermsUpdateV1) response payload. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] +#[non_exhaustive] +pub struct AutomodTermsUpdateV1Payload { + /// The ID of the broadcaster specified in the request. + pub broadcaster_user_id: types::UserId, + /// The login of the broadcaster specified in the request. + pub broadcaster_user_login: types::UserName, + /// The user name of the broadcaster specified in the request. + pub broadcaster_user_name: types::DisplayName, + + /// The ID of the moderator who changed the channel settings. + pub moderator_user_id: types::UserId, + /// The login of the moderator. + pub moderator_user_login: types::UserName, + /// The moderator’s user name. + pub moderator_user_name: types::DisplayName, + + /// The status change applied to the terms. + pub action: AutomodTermAction, + /// Indicates whether this term was added due to an Automod message approve/deny action. + pub from_automod: bool, + /// The list of terms that had a status change. + pub terms: Vec, +} + +/// An action on an Automod term +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[non_exhaustive] +#[serde(rename_all = "snake_case")] +pub enum AutomodTermAction { + /// A term was added to the list of permitted terms + AddPermitted, + /// A term was removed from the list of permitted terms + RemovePermitted, + /// A term was added to the list of blocked terms + AddBlocked, + /// A term was removed to the list of blocked terms + RemoveBlocked, + /// An unknown term action, contains the raw string provided by Twitch. + #[serde(untagged)] + Unknown(String), +} + +#[cfg(test)] +#[test] +fn parse_payload() { + use crate::eventsub::{Event, Message}; + + let payload = r##" + { + "subscription": { + "id": "480cfe69-7aea-4527-b0a7-9704a0bf7294", + "status": "enabled", + "type": "automod.terms.update", + "version": "1", + "condition": { + "broadcaster_user_id": "129546453", + "moderator_user_id": "129546453" + }, + "transport": { + "method": "websocket", + "session_id": "AgoQZ12VWLotRG6u3pudLlbhvhIGY2VsbC1j" + }, + "created_at": "2024-11-03T11:52:05.699721918Z", + "cost": 0 + }, + "event": { + "broadcaster_user_id": "129546453", + "broadcaster_user_login": "nerixyz", + "broadcaster_user_name": "nerixyz", + "moderator_user_id": "129546453", + "moderator_user_login": "nerixyz", + "moderator_user_name": "nerixyz", + "action": "add_permitted", + "from_automod": true, + "terms": [ + "boobs" + ] + } + } + "##; + + let val = Event::parse(payload).unwrap(); + crate::tests::roundtrip(&val); + + let Event::AutomodTermsUpdateV1(val) = val else { + panic!("invalid event type"); + }; + let Message::Notification(notif) = val.message else { + panic!("invalid terms type"); + }; + + assert_eq!(notif.broadcaster_user_id.as_str(), "129546453"); + assert!(notif.from_automod); + assert_eq!(notif.action, AutomodTermAction::AddPermitted); + assert_eq!(notif.terms.len(), 1); + assert_eq!(notif.terms[0], "boobs"); +} diff --git a/src/eventsub/event.rs b/src/eventsub/event.rs index 2debfdf545..19c8043473 100644 --- a/src/eventsub/event.rs +++ b/src/eventsub/event.rs @@ -12,6 +12,9 @@ macro_rules! fill_events { ($callback:ident( $($args:tt)* )) => { $callback!($($args)* automod::AutomodMessageHoldV1; + automod::AutomodMessageUpdateV1; + automod::AutomodSettingsUpdateV1; + automod::AutomodTermsUpdateV1; channel::ChannelAdBreakBeginV1; channel::ChannelBanV1; channel::ChannelCharityCampaignDonateV1; @@ -138,6 +141,12 @@ pub struct EventTypeParseError; make_event_type!("Event Types": pub enum EventType { "a message was caught by automod for review": AutomodMessageHold => "automod.message.hold", + "a message in the automod queue had its status changed": + AutomodMessageUpdate => "automod.message.update", + "a notification is sent when a broadcaster’s automod settings are updated.": + AutomodSettingsUpdate => "automod.settings.update", + "a notification is sent when a broadcaster’s automod terms are updated. Changes to private terms are not sent.": + AutomodTermsUpdate => "automod.terms.update", "a user runs a midroll commercial break, either manually or automatically via ads manager.": ChannelAdBreakBegin => "channel.ad_break.begin", "a moderator or bot clears all messages from the chat room.": @@ -255,6 +264,12 @@ fn main() { pub enum Event { /// Automod Message Hold V1 Event AutomodMessageHoldV1(Payload), + /// Automod Message Update V1 Event + AutomodMessageUpdateV1(Payload), + /// Automod Settings Update V1 Event + AutomodSettingsUpdateV1(Payload), + /// Automod Terms Update V1 Event + AutomodTermsUpdateV1(Payload), /// Channel Ad Break Begin V1 Event ChannelAdBreakBeginV1(Payload), /// Channel Chat Clear V1 Event diff --git a/src/eventsub/mod.rs b/src/eventsub/mod.rs index d926616ae6..59f73b8533 100644 --- a/src/eventsub/mod.rs +++ b/src/eventsub/mod.rs @@ -79,14 +79,14 @@ //! //! //! -//!
automod.* 🟡 1/4 +//!
automod.* 🟢 4/4 //! //! | Name | Subscription
Payload | //! |---|:---| //! | [`automod.message.hold`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodmessagehold) | [AutomodMessageHoldV1](automod::AutomodMessageHoldV1)
[AutomodMessageHoldV1Payload](automod::AutomodMessageHoldV1Payload) | -//! | [`automod.message.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodmessageupdate) | -
- | -//! | [`automod.settings.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodsettingsupdate) | -
- | -//! | [`automod.terms.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodtermsupdate) | -
- | +//! | [`automod.message.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodmessageupdate) | [AutomodMessageUpdateV1](automod::AutomodMessageUpdateV1)
[AutomodMessageUpdateV1Payload](automod::AutomodMessageUpdateV1Payload) | +//! | [`automod.settings.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodsettingsupdate) | [AutomodSettingsUpdateV1](automod::AutomodSettingsUpdateV1)
[AutomodSettingsUpdateV1Payload](automod::AutomodSettingsUpdateV1Payload) | +//! | [`automod.terms.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#automodtermsupdate) | [AutomodTermsUpdateV1](automod::AutomodTermsUpdateV1)
[AutomodTermsUpdateV1Payload](automod::AutomodTermsUpdateV1Payload) | //! //!
//!