twitch_api/eventsub/
event.rs

1//! EventSub events and their types
2#![allow(deprecated)]
3pub mod websocket;
4
5use std::borrow::Cow;
6
7use serde_derive::{Deserialize, Serialize};
8
9use super::*;
10
11macro_rules! fill_events {
12    ($callback:ident( $($args:tt)* )) => {
13        $callback!($($args)*
14            automod::AutomodMessageHoldV1;
15            automod::AutomodMessageHoldV2;
16            automod::AutomodMessageUpdateV1;
17            automod::AutomodMessageUpdateV2;
18            automod::AutomodSettingsUpdateV1;
19            automod::AutomodTermsUpdateV1;
20            channel::ChannelAdBreakBeginV1;
21            channel::ChannelBanV1;
22            channel::ChannelBitsUseV1;
23            channel::ChannelCharityCampaignDonateV1;
24            channel::ChannelCharityCampaignProgressV1;
25            channel::ChannelCharityCampaignStartV1;
26            channel::ChannelCharityCampaignStopV1;
27            channel::ChannelChatClearUserMessagesV1;
28            channel::ChannelChatClearV1;
29            channel::ChannelChatMessageV1;
30            channel::ChannelChatMessageDeleteV1;
31            channel::ChannelChatNotificationV1;
32            channel::ChannelChatUserMessageHoldV1;
33            channel::ChannelChatUserMessageUpdateV1;
34            channel::ChannelChatSettingsUpdateV1;
35            channel::ChannelCheerV1;
36            channel::ChannelFollowV1;
37            channel::ChannelFollowV2;
38            channel::ChannelGoalBeginV1;
39            channel::ChannelGoalEndV1;
40            channel::ChannelGoalProgressV1;
41            #[cfg(feature = "beta")]
42            channel::ChannelGuestStarGuestUpdateBeta;
43            #[cfg(feature = "beta")]
44            channel::ChannelGuestStarSessionBeginBeta;
45            #[cfg(feature = "beta")]
46            channel::ChannelGuestStarSessionEndBeta;
47            #[cfg(feature = "beta")]
48            channel::ChannelGuestStarSettingsUpdateBeta;
49            channel::ChannelHypeTrainBeginV1;
50            channel::ChannelHypeTrainEndV1;
51            channel::ChannelHypeTrainProgressV1;
52            channel::ChannelModerateV1;
53            channel::ChannelModerateV2;
54            channel::ChannelModeratorAddV1;
55            channel::ChannelModeratorRemoveV1;
56            channel::ChannelPointsAutomaticRewardRedemptionAddV1;
57            channel::ChannelPointsCustomRewardAddV1;
58            channel::ChannelPointsCustomRewardRedemptionAddV1;
59            channel::ChannelPointsCustomRewardRedemptionUpdateV1;
60            channel::ChannelPointsCustomRewardRemoveV1;
61            channel::ChannelPointsCustomRewardUpdateV1;
62            channel::ChannelPollBeginV1;
63            channel::ChannelPollEndV1;
64            channel::ChannelPollProgressV1;
65            channel::ChannelPredictionBeginV1;
66            channel::ChannelPredictionEndV1;
67            channel::ChannelPredictionLockV1;
68            channel::ChannelPredictionProgressV1;
69            channel::ChannelRaidV1;
70            channel::ChannelSharedChatBeginV1;
71            channel::ChannelSharedChatEndV1;
72            channel::ChannelSharedChatUpdateV1;
73            channel::ChannelShieldModeBeginV1;
74            channel::ChannelShieldModeEndV1;
75            channel::ChannelShoutoutCreateV1;
76            channel::ChannelShoutoutReceiveV1;
77            channel::ChannelSubscribeV1;
78            channel::ChannelSubscriptionEndV1;
79            channel::ChannelSubscriptionGiftV1;
80            channel::ChannelSubscriptionMessageV1;
81            channel::ChannelSuspiciousUserMessageV1;
82            channel::ChannelSuspiciousUserUpdateV1;
83            channel::ChannelUnbanV1;
84            channel::ChannelUnbanRequestCreateV1;
85            channel::ChannelUnbanRequestResolveV1;
86            channel::ChannelUpdateV1;
87            channel::ChannelUpdateV2;
88            channel::ChannelVipAddV1;
89            channel::ChannelVipRemoveV1;
90            channel::ChannelWarningAcknowledgeV1;
91            channel::ChannelWarningSendV1;
92            conduit::ConduitShardDisabledV1;
93            stream::StreamOfflineV1;
94            stream::StreamOnlineV1;
95            user::UserAuthorizationGrantV1;
96            user::UserAuthorizationRevokeV1;
97            user::UserUpdateV1;
98            user::UserWhisperMessageV1;
99        )
100    };
101}
102
103macro_rules! is_thing {
104    (@inner $s:expr, $thing:ident; $( $(#[$meta:meta])* $module:ident::$event:ident);* $(;)?) => {
105        match $s {
106            $( $(#[$meta])* Event::$event(Payload { message : Message::$thing(..), ..}) => true,)*
107            _ => false,
108        }
109    };
110    ($s:expr, $thing:ident) => {
111        fill_events!(is_thing(@inner $s, $thing;))
112    };
113}
114
115macro_rules! make_event_type {
116    ($enum_docs:literal: pub enum $enum_name:ident {
117        $(
118            $event_docs:literal:
119            $variant_name:ident => $event_name:literal,
120        )*
121    },
122        to_str: $to_str_docs:literal,
123        from_str_error: $from_str_error:ident,
124    ) => {
125        #[doc = $enum_docs]
126        #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
127        #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
128        #[non_exhaustive]
129        pub enum $enum_name {
130            $(
131                #[doc = concat!("`", $event_name, "`: ", $event_docs)]
132                #[serde(rename = $event_name)]
133                $variant_name,
134            )*
135        }
136
137        impl $enum_name {
138            #[doc = $to_str_docs]
139            pub const fn to_str(&self) -> &'static str {
140                use $enum_name::*;
141                match self {
142                    $($variant_name => $event_name,)*
143                }
144            }
145        }
146
147        impl std::str::FromStr for $enum_name {
148            type Err = $from_str_error;
149
150            fn from_str(s: &str) -> Result<Self, Self::Err> {
151                use $enum_name::*;
152                match s {
153                    $($event_name => Ok($variant_name),)*
154                    _ => Err($from_str_error),
155                }
156            }
157        }
158
159        impl std::fmt::Display for $enum_name {
160            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161                f.write_str(self.to_str())
162            }
163        }
164    };
165}
166
167/// Error when parsing an event-type string.
168#[derive(thiserror::Error, Debug, Clone)]
169#[error("Unknown event type")]
170pub struct EventTypeParseError;
171
172make_event_type!("Event Types": pub enum EventType {
173    "a message was caught by automod for review":
174    AutomodMessageHold => "automod.message.hold",
175    "a message in the automod queue had its status changed":
176    AutomodMessageUpdate => "automod.message.update",
177    "a notification is sent when a broadcaster’s automod settings are updated.":
178    AutomodSettingsUpdate => "automod.settings.update",
179    "a notification is sent when a broadcaster’s automod terms are updated. Changes to private terms are not sent.":
180    AutomodTermsUpdate => "automod.terms.update",
181    "a user runs a midroll commercial break, either manually or automatically via ads manager.":
182    ChannelAdBreakBegin => "channel.ad_break.begin",
183    "sends a notification whenever Bits are used on a channel.":
184    ChannelBitsUse => "channel.bits.use",
185    "a moderator or bot clears all messages from the chat room.":
186    ChannelChatClear => "channel.chat.clear",
187    "a moderator or bot clears all messages for a specific user.":
188    ChannelChatClearUserMessages => "channel.chat.clear_user_messages",
189    "any user sends a message to a specific chat room.":
190    ChannelChatMessage => "channel.chat.message",
191    "a moderator removes a specific message.":
192    ChannelChatMessageDelete => "channel.chat.message_delete",
193    "an event that appears in chat occurs, such as someone subscribing to the channel or a subscription is gifted.":
194    ChannelChatNotification => "channel.chat.notification",
195    "a user's message is caught by automod.":
196    ChannelChatUserMessageHold => "channel.chat.user_message_hold",
197    "a user's message's automod status is updated.":
198    ChannelChatUserMessageUpdate => "channel.chat.user_message_update",
199    "a broadcaster’s chat settings are updated.":
200    ChannelChatSettingsUpdate => "channel.chat_settings.update",
201    "a user donates to the broadcaster’s charity campaign.":
202    ChannelCharityCampaignDonate => "channel.charity_campaign.donate",
203    "progress is made towards the campaign’s goal or when the broadcaster changes the fundraising goal.":
204    ChannelCharityCampaignProgress => "channel.charity_campaign.progress",
205    "a broadcaster starts a charity campaign.":
206    ChannelCharityCampaignStart => "channel.charity_campaign.start",
207    "a broadcaster stops a charity campaign.":
208    ChannelCharityCampaignStop => "channel.charity_campaign.stop",
209    "subscription type sends notifications when a broadcaster updates the category, title, mature flag, or broadcast language for their channel.":
210    ChannelUpdate => "channel.update",
211    "a specified channel receives a follow.":
212    ChannelFollow => "channel.follow",
213    "a specified channel receives a subscriber. This does not include resubscribes.":
214    ChannelSubscribe => "channel.subscribe",
215    "a user cheers on the specified channel.":
216    ChannelCheer => "channel.cheer",
217    "a viewer is banned from the specified channel.":
218    ChannelBan => "channel.ban",
219    "a viewer is unbanned from the specified channel.":
220    ChannelUnban => "channel.unban",
221    "a user creates an unban request.":
222    ChannelUnbanRequestCreate => "channel.unban_request.create",
223    "an unban request has been resolved.":
224    ChannelUnbanRequestResolve => "channel.unban_request.resolve",
225    "a viewer has redeemed an automatic channel points reward on the specified channel.":
226    ChannelPointsAutomaticRewardRedemptionAdd => "channel.channel_points_automatic_reward_redemption.add",
227    "a custom channel points reward has been created for the specified channel.":
228    ChannelPointsCustomRewardAdd => "channel.channel_points_custom_reward.add",
229    "a custom channel points reward has been updated for the specified channel.":
230    ChannelPointsCustomRewardUpdate => "channel.channel_points_custom_reward.update",
231    "a custom channel points reward has been removed from the specified channel.":
232    ChannelPointsCustomRewardRemove => "channel.channel_points_custom_reward.remove",
233    "a viewer has redeemed a custom channel points reward on the specified channel.":
234    ChannelPointsCustomRewardRedemptionAdd => "channel.channel_points_custom_reward_redemption.add",
235    "a redemption of a channel points custom reward has been updated for the specified channel.":
236    ChannelPointsCustomRewardRedemptionUpdate => "channel.channel_points_custom_reward_redemption.update",
237    "a poll begins on the specified channel.":
238    ChannelPollBegin => "channel.poll.begin",
239    "a user responds to a poll on the specified channel.":
240    ChannelPollProgress => "channel.poll.progress",
241    "a poll ends on the specified channel.":
242    ChannelPollEnd => "channel.poll.end",
243    "a Prediction begins on the specified channel":
244    ChannelPredictionBegin => "channel.prediction.begin",
245    "a user participates in a Prediction on the specified channel.":
246    ChannelPredictionProgress => "channel.prediction.progress",
247    "a Prediction is locked on the specified channel.":
248    ChannelPredictionLock => "channel.prediction.lock",
249    "a Prediction ends on the specified channel.":
250    ChannelPredictionEnd => "channel.prediction.end",
251    "a specified broadcaster sends a Shoutout.":
252    ChannelShoutoutCreate => "channel.shoutout.create",
253    "a specified broadcaster receives a Shoutout.":
254    ChannelShoutoutReceive => "channel.shoutout.receive",
255    "a broadcaster raids another broadcaster’s channel.":
256    ChannelRaid => "channel.raid",
257    "a channel becomes active in an active shared chat session.":
258    ChannelSharedChatBegin => "channel.shared_chat.begin",
259    "a channel leaves a shared chat session or the session ends.":
260    ChannelSharedChatEnd => "channel.shared_chat.end",
261    "the active shared chat session the channel is in changed.":
262    ChannelSharedChatUpdate => "channel.shared_chat.update",
263    "a subscription to the specified channel expires.":
264    ChannelSubscriptionEnd => "channel.subscription.end",
265    "a user gives one or more gifted subscriptions in a channel.":
266    ChannelSubscriptionGift => "channel.subscription.gift",
267    "a user sends a resubscription chat message in a specific channel":
268    ChannelSubscriptionMessage => "channel.subscription.message",
269    "a chat message has been sent from a suspicious user.":
270    ChannelSuspiciousUserMessage => "channel.suspicious_user.message",
271    "a suspicious user has been updated.":
272    ChannelSuspiciousUserUpdate => "channel.suspicious_user.update",
273    "a channel activates shield mode":
274    ChannelShieldModeBegin => "channel.shield_mode.begin",
275    "a channel deactivates shield mode":
276    ChannelShieldModeEnd => "channel.shield_mode.end",
277    "a goal begins on the specified channel.":
278    ChannelGoalBegin => "channel.goal.begin",
279    "a goal makes progress on the specified channel.":
280    ChannelGoalProgress => "channel.goal.progress",
281    "a goal ends on the specified channel.":
282    ChannelGoalEnd => "channel.goal.end",
283    "the host begins a new Guest Star session.":
284    ChannelGuestStarSessionBegin => "channel.guest_star_session.begin",
285    "a running Guest Star session is ended by the host, or automatically by the system.":
286    ChannelGuestStarSessionEnd => "channel.guest_star_session.end",
287    "the host preferences for Guest Star have been updated.":
288    ChannelGuestStarSettingsUpdate => "channel.guest_star_settings.update",
289    "a guest or a slot is updated in an active Guest Star session.":
290    ChannelGuestStarGuestUpdate => "channel.guest_star_guest.update",
291    "a hype train begins on the specified channel.":
292    ChannelHypeTrainBegin => "channel.hype_train.begin",
293    "a hype train makes progress on the specified channel.":
294    ChannelHypeTrainProgress => "channel.hype_train.progress",
295    "a hype train ends on the specified channel.":
296    ChannelHypeTrainEnd => "channel.hype_train.end",
297    "a moderator performs a moderation action in a channel.":
298    ChannelModerate => "channel.moderate",
299    "a user is given moderator privileges on a specified channel.":
300    ChannelModeratorAdd => "channel.moderator.add",
301    "a user has moderator privileges removed on a specified channel.":
302    ChannelModeratorRemove => "channel.moderator.remove",
303    "a VIP is added to the channel.":
304    ChannelVipAdd => "channel.vip.add",
305    "a warning is acknowledged by a user.":
306    ChannelWarningAcknowledge => "channel.warning.acknowledge",
307    "a warning is sent to a user.":
308    ChannelWarningSend => "channel.warning.send",
309    "a VIP is removed from the channel.":
310    ChannelVipRemove => "channel.vip.remove",
311    "sends a notification when eventsub disables a shard due to the status of the underlying transport changing.":
312    ConduitShardDisabled => "conduit.shard.disabled",
313    "the specified broadcaster starts a stream.":
314    StreamOnline => "stream.online",
315    "the specified broadcaster stops a stream.":
316    StreamOffline => "stream.offline",
317    "user updates their account.":
318    UserUpdate => "user.update",
319    "a user has revoked authorization for your client id. Use this webhook to meet government requirements for handling user data, such as GDPR, LGPD, or CCPA.":
320    UserAuthorizationRevoke => "user.authorization.revoke",
321    "a user’s authorization has been granted to your client id.":
322    UserAuthorizationGrant => "user.authorization.grant",
323    "a user receives a whisper.":
324    UserWhisperMessage => "user.whisper.message",
325},
326    to_str: r#"Get the event string of this event.
327```
328# use twitch_api::eventsub::EventType;
329fn main() {
330    assert_eq!(EventType::ChannelUpdate.to_str(), "channel.update");
331    assert_eq!(EventType::ChannelUnban.to_str(), "channel.unban");
332}
333```"#,
334    from_str_error: EventTypeParseError,
335);
336
337/// A notification with an event payload. Enumerates all possible [`Payload`s](Payload)
338///
339/// Parse with [`Event::parse`] or parse the whole http request your server receives with [`Payload::parse_http`]
340#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
341#[allow(clippy::large_enum_variant)]
342#[non_exhaustive]
343pub enum Event {
344    /// Automod Message Hold V1 Event
345    AutomodMessageHoldV1(Payload<automod::AutomodMessageHoldV1>),
346    /// Automod Message Hold V2 Event
347    AutomodMessageHoldV2(Payload<automod::AutomodMessageHoldV2>),
348    /// Automod Message Update V1 Event
349    AutomodMessageUpdateV1(Payload<automod::AutomodMessageUpdateV1>),
350    /// Automod Message Update V2 Event
351    AutomodMessageUpdateV2(Payload<automod::AutomodMessageUpdateV2>),
352    /// Automod Settings Update V1 Event
353    AutomodSettingsUpdateV1(Payload<automod::AutomodSettingsUpdateV1>),
354    /// Automod Terms Update V1 Event
355    AutomodTermsUpdateV1(Payload<automod::AutomodTermsUpdateV1>),
356    /// Channel Ad Break Begin V1 Event
357    ChannelAdBreakBeginV1(Payload<channel::ChannelAdBreakBeginV1>),
358    /// Channel Bit Use V1 Event
359    ChannelBitsUseV1(Payload<channel::ChannelBitsUseV1>),
360    /// Channel Chat Clear V1 Event
361    ChannelChatClearV1(Payload<channel::ChannelChatClearV1>),
362    /// Channel Chat ClearUserMessages V1 Event
363    ChannelChatClearUserMessagesV1(Payload<channel::ChannelChatClearUserMessagesV1>),
364    /// Channel Chat Message V1 Event
365    ChannelChatMessageV1(Payload<channel::ChannelChatMessageV1>),
366    /// Channel Chat MessageDelete V1 Event
367    ChannelChatMessageDeleteV1(Payload<channel::ChannelChatMessageDeleteV1>),
368    /// Channel Chat Notification V1 Event
369    ChannelChatNotificationV1(Payload<channel::ChannelChatNotificationV1>),
370    /// Channel Chat UserMessageHold V1 Event
371    ChannelChatUserMessageHoldV1(Payload<channel::ChannelChatUserMessageHoldV1>),
372    /// Channel Chat UserMessageUpdate V1 Event
373    ChannelChatUserMessageUpdateV1(Payload<channel::ChannelChatUserMessageUpdateV1>),
374    /// Channel ChatSettings Update V1 Event
375    ChannelChatSettingsUpdateV1(Payload<channel::ChannelChatSettingsUpdateV1>),
376    /// Channel Charity Campaign Donate V1 Event
377    ChannelCharityCampaignDonateV1(Payload<channel::ChannelCharityCampaignDonateV1>),
378    /// Channel Charity Campaign Progress V1 Event
379    ChannelCharityCampaignProgressV1(Payload<channel::ChannelCharityCampaignProgressV1>),
380    /// Channel Charity Campaign Start V1 Event
381    ChannelCharityCampaignStartV1(Payload<channel::ChannelCharityCampaignStartV1>),
382    /// Channel Charity Campaign Stop V1 Event
383    ChannelCharityCampaignStopV1(Payload<channel::ChannelCharityCampaignStopV1>),
384    /// Channel Update V1 Event
385    #[deprecated(note = "use `Event::ChannelUpdateV2` instead")]
386    ChannelUpdateV1(Payload<channel::ChannelUpdateV1>),
387    /// Channel Update V2 Event
388    ChannelUpdateV2(Payload<channel::ChannelUpdateV2>),
389    /// Channel Follow V1 Event
390    #[deprecated(note = "use `Event::ChannelFollowV2` instead")]
391    ChannelFollowV1(Payload<channel::ChannelFollowV1>),
392    /// Channel Follow V2 Event
393    ChannelFollowV2(Payload<channel::ChannelFollowV2>),
394    /// Channel Subscribe V1 Event
395    ChannelSubscribeV1(Payload<channel::ChannelSubscribeV1>),
396    /// Channel Cheer V1 Event
397    ChannelCheerV1(Payload<channel::ChannelCheerV1>),
398    /// Channel Ban V1 Event
399    ChannelBanV1(Payload<channel::ChannelBanV1>),
400    /// Channel Unban V1 Event
401    ChannelUnbanV1(Payload<channel::ChannelUnbanV1>),
402    /// Channel UnbanRequest Create V1 Event
403    ChannelUnbanRequestCreateV1(Payload<channel::ChannelUnbanRequestCreateV1>),
404    /// Channel UnbanRequest Resolve V1 Event
405    ChannelUnbanRequestResolveV1(Payload<channel::ChannelUnbanRequestResolveV1>),
406    /// Channel VIP Add V1 Event
407    ChannelVipAddV1(Payload<channel::ChannelVipAddV1>),
408    /// Channel VIP Remove V1 Event
409    ChannelVipRemoveV1(Payload<channel::ChannelVipRemoveV1>),
410    /// Channel Warning Acknowledge V1 Event
411    ChannelWarningAcknowledgeV1(Payload<channel::ChannelWarningAcknowledgeV1>),
412    /// Channel Warning Send V1 Event
413    ChannelWarningSendV1(Payload<channel::ChannelWarningSendV1>),
414    /// Channel Points Automatic Reward Redemption Add V1 Event
415    ChannelPointsAutomaticRewardRedemptionAddV1(
416        Payload<channel::ChannelPointsAutomaticRewardRedemptionAddV1>,
417    ),
418    /// Channel Points Custom Reward Add V1 Event
419    ChannelPointsCustomRewardAddV1(Payload<channel::ChannelPointsCustomRewardAddV1>),
420    /// Channel Points Custom Reward Update V1 Event
421    ChannelPointsCustomRewardUpdateV1(Payload<channel::ChannelPointsCustomRewardUpdateV1>),
422    /// Channel Points Custom Reward Remove V1 Event
423    ChannelPointsCustomRewardRemoveV1(Payload<channel::ChannelPointsCustomRewardRemoveV1>),
424    /// Channel Points Custom Reward Redemption Add V1 Event
425    ChannelPointsCustomRewardRedemptionAddV1(
426        Payload<channel::ChannelPointsCustomRewardRedemptionAddV1>,
427    ),
428    /// Channel Points Custom Reward Redemption Update V1 Event
429    ChannelPointsCustomRewardRedemptionUpdateV1(
430        Payload<channel::ChannelPointsCustomRewardRedemptionUpdateV1>,
431    ),
432    /// Channel Poll Begin V1 Event
433    ChannelPollBeginV1(Payload<channel::ChannelPollBeginV1>),
434    /// Channel Poll Progress V1 Event
435    ChannelPollProgressV1(Payload<channel::ChannelPollProgressV1>),
436    /// Channel Poll End V1 Event
437    ChannelPollEndV1(Payload<channel::ChannelPollEndV1>),
438    /// Channel Prediction Begin V1 Event
439    ChannelPredictionBeginV1(Payload<channel::ChannelPredictionBeginV1>),
440    /// Channel Prediction Progress V1 Event
441    ChannelPredictionProgressV1(Payload<channel::ChannelPredictionProgressV1>),
442    /// Channel Prediction Lock V1 Event
443    ChannelPredictionLockV1(Payload<channel::ChannelPredictionLockV1>),
444    /// Channel Prediction End V1 Event
445    ChannelPredictionEndV1(Payload<channel::ChannelPredictionEndV1>),
446    /// Channel Raid V1 Event
447    ChannelRaidV1(Payload<channel::ChannelRaidV1>),
448    /// Channel SharedChat Begin V1 Event
449    ChannelSharedChatBeginV1(Payload<channel::ChannelSharedChatBeginV1>),
450    /// Channel SharedChat End V1 Event
451    ChannelSharedChatEndV1(Payload<channel::ChannelSharedChatEndV1>),
452    /// Channel SharedChat Update V1 Event
453    ChannelSharedChatUpdateV1(Payload<channel::ChannelSharedChatUpdateV1>),
454    /// Channel ShieldMode Begin V1 Event
455    ChannelShieldModeBeginV1(Payload<channel::ChannelShieldModeBeginV1>),
456    /// Channel ShieldMode End V1 Event
457    ChannelShieldModeEndV1(Payload<channel::ChannelShieldModeEndV1>),
458    /// Channel Shoutout Create V1 Event
459    ChannelShoutoutCreateV1(Payload<channel::ChannelShoutoutCreateV1>),
460    /// Channel Shoutout Receive V1 Event
461    ChannelShoutoutReceiveV1(Payload<channel::ChannelShoutoutReceiveV1>),
462    /// Channel SuspicousUser Message V1 Event
463    ChannelSuspiciousUserMessageV1(Payload<channel::ChannelSuspiciousUserMessageV1>),
464    /// Channel SuspicousUser Update V1 Event
465    ChannelSuspiciousUserUpdateV1(Payload<channel::ChannelSuspiciousUserUpdateV1>),
466    /// Channel Goal Begin V1 Event
467    ChannelGoalBeginV1(Payload<channel::ChannelGoalBeginV1>),
468    /// Channel Goal Progress V1 Event
469    ChannelGoalProgressV1(Payload<channel::ChannelGoalProgressV1>),
470    /// Channel Goal End V1 Event
471    ChannelGoalEndV1(Payload<channel::ChannelGoalEndV1>),
472    /// Channel GuestStarSession Begin V1 Event
473    #[cfg(feature = "beta")]
474    ChannelGuestStarSessionBeginBeta(Payload<channel::ChannelGuestStarSessionBeginBeta>),
475    /// Channel GuestStarSession End V1 Event
476    #[cfg(feature = "beta")]
477    ChannelGuestStarSessionEndBeta(Payload<channel::ChannelGuestStarSessionEndBeta>),
478    /// Channel GuestStarSettings Update V1 Event
479    #[cfg(feature = "beta")]
480    ChannelGuestStarSettingsUpdateBeta(Payload<channel::ChannelGuestStarSettingsUpdateBeta>),
481    /// Channel GuestStarGuest Update V1 Event
482    #[cfg(feature = "beta")]
483    ChannelGuestStarGuestUpdateBeta(Payload<channel::ChannelGuestStarGuestUpdateBeta>),
484    /// Channel Hype Train Begin V1 Event
485    ChannelHypeTrainBeginV1(Payload<channel::ChannelHypeTrainBeginV1>),
486    /// Channel Hype Train Progress V1 Event
487    ChannelHypeTrainProgressV1(Payload<channel::ChannelHypeTrainProgressV1>),
488    /// Channel Hype Train End V1 Event
489    ChannelHypeTrainEndV1(Payload<channel::ChannelHypeTrainEndV1>),
490    /// Channel Moderate V1 Event
491    ChannelModerateV1(Payload<channel::ChannelModerateV1>),
492    /// Channel Moderate V2 Event
493    ChannelModerateV2(Payload<channel::ChannelModerateV2>),
494    /// Channel Moderator Add V1 Event
495    ChannelModeratorAddV1(Payload<channel::ChannelModeratorAddV1>),
496    /// Channel Moderator Remove V1 Event
497    ChannelModeratorRemoveV1(Payload<channel::ChannelModeratorRemoveV1>),
498    /// Conduit Shard Disabled V1 Event
499    ConduitShardDisabledV1(Payload<conduit::ConduitShardDisabledV1>),
500    /// StreamOnline V1 Event
501    StreamOnlineV1(Payload<stream::StreamOnlineV1>),
502    /// StreamOffline V1 Event
503    StreamOfflineV1(Payload<stream::StreamOfflineV1>),
504    /// User Update V1 Event
505    UserUpdateV1(Payload<user::UserUpdateV1>),
506    /// User Authorization Grant V1 Event
507    UserAuthorizationGrantV1(Payload<user::UserAuthorizationGrantV1>),
508    /// User Authorization Revoke V1 Event
509    UserAuthorizationRevokeV1(Payload<user::UserAuthorizationRevokeV1>),
510    /// User Whisper Message V1 Event
511    UserWhisperMessageV1(Payload<user::UserWhisperMessageV1>),
512    /// Channel Subscription End V1 Event
513    ChannelSubscriptionEndV1(Payload<channel::ChannelSubscriptionEndV1>),
514    /// Channel Subscription Gift V1 Event
515    ChannelSubscriptionGiftV1(Payload<channel::ChannelSubscriptionGiftV1>),
516    /// Channel Subscription Message V1 Event
517    ChannelSubscriptionMessageV1(Payload<channel::ChannelSubscriptionMessageV1>),
518}
519
520impl Event {
521    /// Parse string slice as an [`Event`]. Consider using [`Event::parse_http`] instead.
522    pub fn parse(source: &str) -> Result<Self, PayloadParseError> {
523        let (version, ty, message_type) =
524            get_version_event_type_and_message_type_from_text(source)?;
525        Self::parse_request(version, &ty, message_type, source.as_bytes().into())
526    }
527
528    /// Returns `true` if the message in the [`Payload`] is [`Notification`].
529    ///
530    /// [`Notification`]: Message::Notification
531    pub const fn is_notification(&self) -> bool { is_thing!(self, Notification) }
532
533    /// Returns `true` if the message in the [`Payload`] is [`Revocation`].
534    ///
535    /// [`Revocation`]: Message::Revocation
536    pub const fn is_revocation(&self) -> bool { is_thing!(self, Revocation) }
537
538    /// Returns `true` if the message in the [`Payload`] is [`VerificationRequest`].
539    ///
540    /// [`VerificationRequest`]: Message::VerificationRequest
541    pub const fn is_verification_request(&self) -> bool { is_thing!(self, VerificationRequest) }
542
543    /// If this event is a [`VerificationRequest`], return the [`VerificationRequest`] message, including the message.
544    #[rustfmt::skip]
545    pub const fn get_verification_request(&self) -> Option<&VerificationRequest> {
546        macro_rules! match_event {
547            ($($(#[$meta:meta])* $module:ident::$event:ident);* $(;)?) => {{
548
549                #[deny(unreachable_patterns)]
550                match &self {
551                    $(  $(#[$meta])* Event::$event(Payload { message: Message::VerificationRequest(v), ..}) => Some(v),)*
552                    _ => None,
553                }
554            }}
555        }
556        fill_events!(match_event())
557    }
558
559    /// Make a [`EventSubSubscription`] from this notification.
560    pub fn subscription(&self) -> Result<EventSubSubscription, serde_json::Error> {
561        macro_rules! match_event {
562            ($($(#[$meta:meta])* $module:ident::$event:ident);* $(;)?) => {{
563                match &self {
564                    $(
565                        $(#[$meta])*
566                        Event::$event(notif) => Ok({
567                            let self::Payload {subscription, ..} = notif; // FIXME: Use @ pattern-binding, currently stable
568
569                            EventSubSubscription {
570                            cost: subscription.cost,
571                            condition: subscription.condition.condition()?,
572                            created_at: subscription.created_at.clone(),
573                            id: subscription.id.clone(),
574                            status: subscription.status.clone(),
575                            transport: subscription.transport.clone(),
576                            type_: notif.get_event_type(),
577                            version: notif.get_event_version().to_owned(),
578                        }}),
579                    )*
580                }
581            }}
582        }
583
584        fill_events!(match_event())
585    }
586
587    /// Verify that this event is authentic using `HMAC-SHA256`.
588    ///
589    /// HMAC key is `secret`, HMAC message is a concatenation of `Twitch-Eventsub-Message-Id` header, `Twitch-Eventsub-Message-Timestamp` header and the request body.
590    /// HMAC signature is `Twitch-Eventsub-Message-Signature` header.
591    #[cfg(feature = "hmac")]
592    #[cfg_attr(nightly, doc(cfg(feature = "hmac")))]
593    #[must_use]
594    pub fn verify_payload<B>(request: &http::Request<B>, secret: &[u8]) -> bool
595    where B: AsRef<[u8]> {
596        use crypto_hmac::{Hmac, Mac};
597
598        fn message_and_signature<B>(request: &http::Request<B>) -> Option<(Vec<u8>, Vec<u8>)>
599        where B: AsRef<[u8]> {
600            static SHA_HEADER: &str = "sha256=";
601
602            let id = request
603                .headers()
604                .get("Twitch-Eventsub-Message-Id")?
605                .as_bytes();
606            let timestamp = request
607                .headers()
608                .get("Twitch-Eventsub-Message-Timestamp")?
609                .as_bytes();
610            let body = request.body().as_ref();
611
612            let mut message = Vec::with_capacity(id.len() + timestamp.len() + body.len());
613            message.extend_from_slice(id);
614            message.extend_from_slice(timestamp);
615            message.extend_from_slice(body);
616
617            let signature = request
618                .headers()
619                .get("Twitch-Eventsub-Message-Signature")?
620                .to_str()
621                .ok()?;
622            if !signature.starts_with(SHA_HEADER) {
623                return None;
624            }
625            let signature = signature.split_at(SHA_HEADER.len()).1;
626            if signature.len() % 2 == 0 {
627                // Convert signature to [u8] from hex digits
628                // Hex decode inspired by https://stackoverflow.com/a/52992629
629                let signature = ((0..signature.len())
630                    .step_by(2)
631                    .map(|i| u8::from_str_radix(&signature[i..i + 2], 16))
632                    .collect::<Result<Vec<u8>, _>>())
633                .ok()?;
634
635                Some((message, signature))
636            } else {
637                None
638            }
639        }
640
641        if let Some((message, signature)) = message_and_signature(request) {
642            let mut mac = Hmac::<sha2::Sha256>::new_from_slice(secret).expect("");
643            mac.update(&message);
644            mac.verify(crypto_hmac::digest::generic_array::GenericArray::from_slice(&signature))
645                .is_ok()
646        } else {
647            false
648        }
649    }
650}
651
652/// Helper function to get version and type of event from text.
653#[allow(clippy::type_complexity)]
654fn get_version_event_type_and_message_type_from_text(
655    source: &str,
656) -> Result<(Cow<'_, str>, EventType, Cow<'_, [u8]>), PayloadParseError> {
657    #[derive(Deserialize)]
658    struct IEventSubscripionInformation {
659        // condition: serde_json::Value,
660        // created_at: types::Timestamp,
661        // status: Status,
662        // cost: usize,
663        // id: types::EventSubId,
664        // transport: TransportResponse,
665        #[serde(rename = "type")]
666        type_: EventType,
667        version: String,
668    }
669    #[derive(Deserialize)]
670    struct IEvent {
671        subscription: IEventSubscripionInformation,
672        challenge: Option<serde_json::Value>,
673        event: Option<serde_json::Value>,
674    }
675
676    let IEvent {
677        subscription,
678        challenge,
679        event,
680    } = parse_json(source, false)?;
681    // FIXME: A visitor is really what we want.
682    if event.is_some() {
683        Ok((
684            subscription.version.into(),
685            subscription.type_,
686            Cow::Borrowed(b"notification"),
687        ))
688    } else if challenge.is_some() {
689        Ok((
690            subscription.version.into(),
691            subscription.type_,
692            Cow::Borrowed(b"webhook_callback_verification"),
693        ))
694    } else {
695        Ok((
696            subscription.version.into(),
697            subscription.type_,
698            Cow::Borrowed(b"revocation"),
699        ))
700    }
701}
702
703/// Helper function to get version and type of event from http.
704#[allow(clippy::type_complexity)]
705fn get_version_event_type_and_message_type_from_http<B>(
706    request: &http::Request<B>,
707) -> Result<(Cow<'_, str>, EventType, Cow<'_, [u8]>), PayloadParseError>
708where B: AsRef<[u8]> {
709    use serde::{de::IntoDeserializer, Deserialize};
710    match (
711        request
712            .headers()
713            .get("Twitch-Eventsub-Subscription-Type")
714            .map(|v| v.as_bytes())
715            .map(std::str::from_utf8)
716            .transpose()?,
717        request
718            .headers()
719            .get("Twitch-Eventsub-Subscription-Version")
720            .map(|v| v.as_bytes())
721            .map(std::str::from_utf8)
722            .transpose()?,
723        request
724            .headers()
725            .get("Twitch-Eventsub-Message-Type")
726            .map(|v| v.as_bytes()),
727    ) {
728        (Some(ty), Some(version), Some(message_type)) => Ok((
729            version.into(),
730            EventType::deserialize(ty.into_deserializer()).map_err(
731                |_: serde::de::value::Error| PayloadParseError::UnknownEventType(ty.to_owned()),
732            )?,
733            message_type.into(),
734        )),
735        (..) => Err(PayloadParseError::MalformedEvent),
736    }
737}
738
739impl Event {
740    /// Parse a http payload as an [`Event`]
741    ///
742    /// Create the webhook via [`CreateEventSubSubscription`](crate::helix::eventsub::CreateEventSubSubscriptionRequest) according to the [Eventsub WebHooks guide](https://dev.twitch.tv/docs/eventsub/handling-webhook-events)
743    pub fn parse_http<B>(request: &http::Request<B>) -> Result<Self, PayloadParseError>
744    where B: AsRef<[u8]> {
745        let (version, ty, message_type) =
746            get_version_event_type_and_message_type_from_http(request)?;
747        let source = request.body().as_ref().into();
748        Self::parse_request(version, &ty, message_type, source)
749    }
750
751    /// Parse a string slice as an [`Event`]. You should not use this, instead, use [`Event::parse_http`] or [`Event::parse`].
752    #[doc(hidden)]
753    pub fn parse_request<'a>(
754        version: Cow<'a, str>,
755        event_type: &'a EventType,
756        message_type: Cow<'a, [u8]>,
757        source: Cow<'a, [u8]>,
758    ) -> Result<Self, PayloadParseError> {
759        /// Match on all defined eventsub types.
760        ///
761        /// If this is not done, we'd get a much worse error message.
762        macro_rules! match_event {
763            ($($(#[$meta:meta])* $module:ident::$event:ident);* $(;)?) => {{
764
765                #[deny(unreachable_patterns)]
766                match (version.as_ref(), event_type) {
767                    $(  $(#[$meta])* (<$module::$event as EventSubscription>::VERSION, &<$module::$event as EventSubscription>::EVENT_TYPE) => {
768                        Event::$event(Payload::parse_request(message_type, source)?)
769                    }  )*
770                    (v, e) => return Err(PayloadParseError::UnimplementedEvent{version: v.to_owned(), event_type: e.clone()})
771                }
772            }}
773        }
774
775        Ok(fill_events!(match_event()))
776    }
777
778    /// Parse a websocket frame as an [`EventsubWebsocketData`]
779    ///
780    /// Create the websocket via [`CreateEventSubSubscription`](crate::helix::eventsub::CreateEventSubSubscriptionRequest) according to the [Eventsub WebSocket guide](https://dev.twitch.tv/docs/eventsub/handling-websocket-events)
781    ///
782    /// # Examples
783    ///
784    /// ```rust
785    /// use twitch_api::eventsub::{Event, EventsubWebsocketData};
786    /// let notification = r#"
787    /// {
788    ///     "metadata": {
789    ///         "message_id": "befa7b53-d79d-478f-86b9-120f112b044e",
790    ///         "message_type": "notification",
791    ///         "message_timestamp": "2019-11-16T10:11:12.123Z",
792    ///         "subscription_type": "channel.follow",
793    ///         "subscription_version": "1"
794    ///     },
795    ///     "payload": {
796    ///         "subscription": {
797    ///             "id": "f1c2a387-161a-49f9-a165-0f21d7a4e1c4",
798    ///             "status": "enabled",
799    ///             "type": "channel.follow",
800    ///             "version": "1",
801    ///             "cost": 1,
802    ///             "condition": {
803    ///                 "broadcaster_user_id": "12826"
804    ///             },
805    ///             "transport": {
806    ///                 "method": "websocket",
807    ///                 "session_id": "AQoQexAWVYKSTIu4ec_2VAxyuhAB"
808    ///             },
809    ///             "created_at": "2019-11-16T10:11:12.123Z"
810    ///         },
811    ///         "event": {
812    ///             "user_id": "1337",
813    ///             "user_login": "awesome_user",
814    ///             "user_name": "Awesome_User",
815    ///             "broadcaster_user_id": "12826",
816    ///             "broadcaster_user_login": "twitch",
817    ///             "broadcaster_user_name": "Twitch",
818    ///             "followed_at": "2020-07-15T18:16:11.17106713Z"
819    ///         }
820    ///     }
821    /// }
822    /// "#;
823    /// let event: EventsubWebsocketData<'_> =
824    ///     Event::parse_websocket(notification)?;
825    /// # Ok::<(), Box<dyn std::error::Error>>(())
826    /// ```
827    pub fn parse_websocket(frame: &str) -> Result<EventsubWebsocketData<'_>, PayloadParseError> {
828        #[derive(Deserialize)]
829        #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
830        struct EventsubWebsocketFrame<'a> {
831            metadata: EventsubWebsocketMetadata<'a>,
832            #[serde(borrow)]
833            payload: &'a serde_json::value::RawValue,
834        }
835
836        let frame: EventsubWebsocketFrame = crate::parse_json(frame, true)?;
837
838        macro_rules! match_event {
839            ($metadata:expr, $message_type:literal, $($(#[$meta:meta])* $module:ident::$event:ident);* $(;)?) => {{
840
841                #[deny(unreachable_patterns)]
842                match ($metadata.subscription_version.as_ref(), &$metadata.subscription_type) {
843                    $(  $(#[$meta])* (<$module::$event as EventSubscription>::VERSION, &<$module::$event as EventSubscription>::EVENT_TYPE) => {
844                        Event::$event(Payload::parse_request_str($message_type.as_ref(), frame.payload.get())?)
845                    }  )*
846                    (v, e) => return Err(PayloadParseError::UnimplementedEvent{version: v.to_owned(), event_type: e.clone()})
847                }
848            }}
849        }
850
851        match frame.metadata {
852            EventsubWebsocketMetadata::Notification(notification) => {
853                let event = fill_events!(match_event(notification, "notification",));
854                Ok(EventsubWebsocketData::Notification {
855                    metadata: notification,
856                    payload: event,
857                })
858            }
859            EventsubWebsocketMetadata::Revocation(revocation) => {
860                let event = fill_events!(match_event(revocation, "revocation",));
861                Ok(EventsubWebsocketData::Revocation {
862                    metadata: revocation,
863                    payload: event,
864                })
865            }
866            EventsubWebsocketMetadata::Welcome(welcome) => Ok(EventsubWebsocketData::Welcome {
867                metadata: welcome,
868                payload: crate::parse_json(frame.payload.get(), true)?,
869            }),
870            EventsubWebsocketMetadata::Keepalive(keepalive) => {
871                Ok(EventsubWebsocketData::Keepalive {
872                    metadata: keepalive,
873                    payload: (),
874                })
875            }
876            EventsubWebsocketMetadata::Reconnect(reconnect) => {
877                Ok(EventsubWebsocketData::Reconnect {
878                    metadata: reconnect,
879                    payload: crate::parse_json(frame.payload.get(), true)?,
880                })
881            }
882        }
883    }
884}