From 866de1f37c2cf2c192793450c7653e0aa1aed147 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 28 Nov 2024 19:41:49 +0100 Subject: [PATCH] feat(eventsub): add `channel.guest_star_guest.update` --- src/eventsub/channel/guest_star_guest/mod.rs | 34 ++++ .../channel/guest_star_guest/update.rs | 183 ++++++++++++++++++ src/eventsub/channel/mod.rs | 6 + src/eventsub/event.rs | 7 + src/eventsub/mod.rs | 4 +- 5 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 src/eventsub/channel/guest_star_guest/mod.rs create mode 100644 src/eventsub/channel/guest_star_guest/update.rs diff --git a/src/eventsub/channel/guest_star_guest/mod.rs b/src/eventsub/channel/guest_star_guest/mod.rs new file mode 100644 index 0000000000..a0053c511c --- /dev/null +++ b/src/eventsub/channel/guest_star_guest/mod.rs @@ -0,0 +1,34 @@ +#![doc(alias = "channel.guest_star_guest")] +//! Events regarding guests of guest star sessions +use super::{EventSubscription, EventType}; +use crate::types; +use serde_derive::{Deserialize, Serialize}; + +pub mod update; + +#[doc(inline)] +pub use update::{ChannelGuestStarGuestUpdateBeta, ChannelGuestStarGuestUpdateBetaPayload}; + +/// The current state of a user in a guest star session +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[non_exhaustive] +#[serde(rename_all = "snake_case")] +pub enum GuestState { + /// The guest has transitioned to the invite queue. + /// + /// This can take place when the guest was previously assigned a slot, but have been removed from the call and are sent back to the invite queue. + Invited, + /// The guest has accepted the invite and is currently in the process of setting up to join the session. + Accepted, + /// The guest has signaled they are ready and can be assigned a slot. + Ready, + /// The guest has been assigned a slot in the session, but is not currently seen live in the broadcasting software. + Backstage, + /// The guest is now live in the host's broadcasting software. + Live, + /// The guest was removed from the call or queue. + Removed, + /// An unknown state, contains the raw string provided by Twitch. + #[serde(untagged)] + Unknown(String), +} diff --git a/src/eventsub/channel/guest_star_guest/update.rs b/src/eventsub/channel/guest_star_guest/update.rs new file mode 100644 index 0000000000..f48beb1be6 --- /dev/null +++ b/src/eventsub/channel/guest_star_guest/update.rs @@ -0,0 +1,183 @@ +#![doc(alias = "channel.guest_star_guest.update")] +//! the host preferences for Guest Star have been updated. + +use super::*; +/// [`channel.guest_star_guest.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelguest_star_guestupdate): the host preferences for Guest Star have been 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 ChannelGuestStarGuestUpdateBeta { + /// The broadcaster user ID for the channel you want to receive Guest Star guest update notifications for. + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub broadcaster_user_id: types::UserId, + /// The user ID of the moderator or broadcaster of the specified channel. + #[cfg_attr(feature = "typed-builder", builder(setter(into)))] + pub moderator_user_id: types::UserId, +} + +impl ChannelGuestStarGuestUpdateBeta { + /// Get notifications for guest star sessions 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 ChannelGuestStarGuestUpdateBeta { + type Payload = ChannelGuestStarGuestUpdateBetaPayload; + + const EVENT_TYPE: EventType = EventType::ChannelGuestStarGuestUpdate; + #[cfg(feature = "twitch_oauth2")] + const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![any( + twitch_oauth2::Scope::ChannelReadGuestStar, + twitch_oauth2::Scope::ChannelManageGuestStar, + twitch_oauth2::Scope::ModeratorReadGuestStar, + twitch_oauth2::Scope::ModeratorManageGuestStart, // XXX: this is wrong + )]; + const VERSION: &'static str = "beta"; +} + +/// [`channel.guest_star_guest.update`](ChannelGuestStarGuestUpdateBeta) response payload. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] +#[non_exhaustive] +pub struct ChannelGuestStarGuestUpdateBetaPayload { + /// The non-host broadcaster user ID. + pub broadcaster_user_id: types::UserId, + /// The non-host broadcaster display name. + pub broadcaster_user_login: types::UserName, + /// The non-host broadcaster login. + pub broadcaster_user_name: types::DisplayName, + + /// ID representing the unique session that was started. + pub session_id: types::GuestStarSessionId, + + /// The user ID of the moderator who updated the guest’s state (could be the host). + /// + /// [None] if the update was performed by the guest. + pub moderator_user_id: Option, + /// The moderator display name. + /// + /// [None] if the update was performed by the guest. + pub moderator_user_login: Option, + /// The moderator login. + /// + /// [None] if the update was performed by the guest. + pub moderator_user_name: Option, + + /// The user ID of the guest who transitioned states in the session. + /// + /// [None] if the slot is now empty. + pub guest_user_id: Option, + /// The guest display name. + /// + /// [None] if the slot is now empty. + pub guest_user_login: Option, + /// The guest login. + /// + /// [None] if the slot is now empty. + pub guest_user_name: Option, + + /// The ID of the slot assignment the guest is assigned to. + /// + /// [None] if the guest is in the [Invited][GuestState::Invited], [Removed][GuestState::Removed], [Ready][GuestState::Ready], or [Accepted][GuestState::Accepted] state. + pub slot_id: Option, + /// The current state of the user after the update has taken place. + /// + /// [None] if the slot is now empty. + pub state: Option, + + /// User ID of the host channel. + pub host_user_id: types::UserId, + /// The host display name. + pub host_user_login: types::UserName, + /// The host login. + pub host_user_name: types::DisplayName, + + /// Flag that signals whether the host is allowing the slot’s video to be seen by participants within the session. + /// + /// [None] if the guest is not slotted. + pub host_video_enabled: Option, + /// Flag that signals whether the host is allowing the slot’s audio to be heard by participants within the session. + /// + /// [None] if the guest is not slotted. + pub host_audio_enabled: Option, + /// Value between 0-100 that represents the slot’s audio level as heard by participants within the session. + /// + /// [None] if the guest is not slotted. + pub host_volume: Option, +} + +#[cfg(test)] +#[test] +fn parse_payload() { + use crate::eventsub::{Event, Message}; + + let payload = r##" + { + "subscription": { + "id": "f1c2a387-161a-49f9-a165-0f21d7a4e1c4", + "type": "channel.guest_star_guest.update", + "version": "beta", + "status": "enabled", + "cost": 0, + "condition": { + "broadcaster_user_id": "1337", + "moderator_user_id": "1312" + }, + "transport": { + "method": "webhook", + "callback": "https://example.com/webhooks/callback" + }, + "created_at": "2023-04-11T10:11:32.123Z" + }, + "event": { + "broadcaster_user_id": "1337", + "broadcaster_user_name": "Cool_User", + "broadcaster_user_login": "cool_user", + "session_id": "2KFRQbFtpmfyD3IevNRnCzOPRJI", + "moderator_user_id": "1312", + "moderator_user_name": "Cool_Mod", + "moderator_user_login": "cool_mod", + "guest_user_id": "1234", + "guest_user_name": "Cool_Guest", + "guest_user_login": "cool_guest", + "slot_id": "1", + "state": "live", + "host_user_id": "4242", + "host_user_name": "A_host", + "host_user_login": "a_host", + "host_video_enabled": true, + "host_audio_enabled": true, + "host_volume": 100 + } + } + "##; + + let val = Event::parse(payload).unwrap(); + crate::tests::roundtrip(&val); + + let Event::ChannelGuestStarGuestUpdateBeta(val) = val else { + panic!("invalid event type"); + }; + let Message::Notification(notif) = val.message else { + panic!("invalid guest type"); + }; + + assert_eq!(notif.broadcaster_user_id.as_str(), "1337"); + assert_eq!(notif.moderator_user_id.unwrap().as_str(), "1312"); + assert_eq!(notif.session_id.as_str(), "2KFRQbFtpmfyD3IevNRnCzOPRJI"); + assert_eq!(notif.guest_user_id.unwrap().as_str(), "1234"); + assert_eq!(notif.host_user_id.as_str(), "4242"); + assert_eq!(notif.slot_id.unwrap().as_str(), "1"); + assert_eq!(notif.state, Some(GuestState::Live)); + assert_eq!(notif.host_video_enabled, Some(true)); + assert_eq!(notif.host_audio_enabled, Some(true)); + assert_eq!(notif.host_volume, Some(100)); +} diff --git a/src/eventsub/channel/mod.rs b/src/eventsub/channel/mod.rs index 9917be243b..76ca281dda 100644 --- a/src/eventsub/channel/mod.rs +++ b/src/eventsub/channel/mod.rs @@ -17,6 +17,8 @@ pub mod cheer; pub mod follow; pub mod goal; #[cfg(feature = "beta")] +pub mod guest_star_guest; +#[cfg(feature = "beta")] pub mod guest_star_session; #[cfg(feature = "beta")] pub mod guest_star_settings; @@ -104,6 +106,10 @@ pub use goal::{ChannelGoalBeginV1, ChannelGoalBeginV1Payload}; pub use goal::{ChannelGoalEndV1, ChannelGoalEndV1Payload}; #[doc(inline)] pub use goal::{ChannelGoalProgressV1, ChannelGoalProgressV1Payload}; +#[cfg(feature = "beta")] +pub use guest_star_guest::{ + ChannelGuestStarGuestUpdateBeta, ChannelGuestStarGuestUpdateBetaPayload, +}; #[doc(inline)] #[cfg(feature = "beta")] pub use guest_star_session::{ diff --git a/src/eventsub/event.rs b/src/eventsub/event.rs index fe2f3064cf..f9b2421421 100644 --- a/src/eventsub/event.rs +++ b/src/eventsub/event.rs @@ -40,6 +40,8 @@ macro_rules! fill_events { channel::ChannelGoalEndV1; channel::ChannelGoalProgressV1; #[cfg(feature = "beta")] + channel::ChannelGuestStarGuestUpdateBeta; + #[cfg(feature = "beta")] channel::ChannelGuestStarSessionBeginBeta; #[cfg(feature = "beta")] channel::ChannelGuestStarSessionEndBeta; @@ -282,6 +284,8 @@ make_event_type!("Event Types": pub enum EventType { ChannelGuestStarSessionEnd => "channel.guest_star_session.end", "the host preferences for Guest Star have been updated.": ChannelGuestStarSettingsUpdate => "channel.guest_star_settings.update", + "a guest or a slot is updated in an active Guest Star session.": + ChannelGuestStarGuestUpdate => "channel.guest_star_guest.update", "a hype train begins on the specified channel.": ChannelHypeTrainBegin => "channel.hype_train.begin", "a hype train makes progress on the specified channel.": @@ -470,6 +474,9 @@ pub enum Event { /// Channel GuestStarSettings Update V1 Event #[cfg(feature = "beta")] ChannelGuestStarSettingsUpdateBeta(Payload), + /// Channel GuestStarGuest Update V1 Event + #[cfg(feature = "beta")] + ChannelGuestStarGuestUpdateBeta(Payload), /// Channel Hype Train Begin V1 Event ChannelHypeTrainBeginV1(Payload), /// Channel Hype Train Progress V1 Event diff --git a/src/eventsub/mod.rs b/src/eventsub/mod.rs index 11de941397..e32a1378bf 100644 --- a/src/eventsub/mod.rs +++ b/src/eventsub/mod.rs @@ -92,7 +92,7 @@ //! //! //! -//!
channel.* 🟡 64/65 +//!
channel.* 🟢 65/65 //! //! | Name | Subscription
Payload | //! |---|:---| @@ -121,7 +121,7 @@ //! | [`channel.goal.begin`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelgoalbegin) (v1) | [ChannelGoalBeginV1](channel::ChannelGoalBeginV1)
[ChannelGoalBeginV1Payload](channel::ChannelGoalBeginV1Payload) | //! | [`channel.goal.end`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelgoalend) (v1) | [ChannelGoalEndV1](channel::ChannelGoalEndV1)
[ChannelGoalEndV1Payload](channel::ChannelGoalEndV1Payload) | //! | [`channel.goal.progress`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelgoalprogress) (v1) | [ChannelGoalProgressV1](channel::ChannelGoalProgressV1)
[ChannelGoalProgressV1Payload](channel::ChannelGoalProgressV1Payload) | -//! | [`channel.guest_star_guest.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelguest_star_guestupdate) (beta) | -
- | +//! | [`channel.guest_star_guest.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelguest_star_guestupdate) (beta) | [ChannelGuestStarGuestUpdateBeta](channel::ChannelGuestStarGuestUpdateBeta)
[ChannelGuestStarGuestUpdateBetaPayload](channel::ChannelGuestStarGuestUpdateBetaPayload) | //! | [`channel.guest_star_session.begin`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelguest_star_sessionbegin) (beta) | [ChannelGuestStarSessionBeginBeta](channel::ChannelGuestStarSessionBeginBeta)
[ChannelGuestStarSessionBeginBetaPayload](channel::ChannelGuestStarSessionBeginBetaPayload) | //! | [`channel.guest_star_session.end`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelguest_star_sessionend) (beta) | [ChannelGuestStarSessionEndBeta](channel::ChannelGuestStarSessionEndBeta)
[ChannelGuestStarSessionEndBetaPayload](channel::ChannelGuestStarSessionEndBetaPayload) | //! | [`channel.guest_star_settings.update`](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#channelguest_star_settingsupdate) (beta) | [ChannelGuestStarSettingsUpdateBeta](channel::ChannelGuestStarSettingsUpdateBeta)
[ChannelGuestStarSettingsUpdateBetaPayload](channel::ChannelGuestStarSettingsUpdateBetaPayload) |