1#![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#[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#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
341#[allow(clippy::large_enum_variant)]
342#[non_exhaustive]
343pub enum Event {
344 AutomodMessageHoldV1(Payload<automod::AutomodMessageHoldV1>),
346 AutomodMessageHoldV2(Payload<automod::AutomodMessageHoldV2>),
348 AutomodMessageUpdateV1(Payload<automod::AutomodMessageUpdateV1>),
350 AutomodMessageUpdateV2(Payload<automod::AutomodMessageUpdateV2>),
352 AutomodSettingsUpdateV1(Payload<automod::AutomodSettingsUpdateV1>),
354 AutomodTermsUpdateV1(Payload<automod::AutomodTermsUpdateV1>),
356 ChannelAdBreakBeginV1(Payload<channel::ChannelAdBreakBeginV1>),
358 ChannelBitsUseV1(Payload<channel::ChannelBitsUseV1>),
360 ChannelChatClearV1(Payload<channel::ChannelChatClearV1>),
362 ChannelChatClearUserMessagesV1(Payload<channel::ChannelChatClearUserMessagesV1>),
364 ChannelChatMessageV1(Payload<channel::ChannelChatMessageV1>),
366 ChannelChatMessageDeleteV1(Payload<channel::ChannelChatMessageDeleteV1>),
368 ChannelChatNotificationV1(Payload<channel::ChannelChatNotificationV1>),
370 ChannelChatUserMessageHoldV1(Payload<channel::ChannelChatUserMessageHoldV1>),
372 ChannelChatUserMessageUpdateV1(Payload<channel::ChannelChatUserMessageUpdateV1>),
374 ChannelChatSettingsUpdateV1(Payload<channel::ChannelChatSettingsUpdateV1>),
376 ChannelCharityCampaignDonateV1(Payload<channel::ChannelCharityCampaignDonateV1>),
378 ChannelCharityCampaignProgressV1(Payload<channel::ChannelCharityCampaignProgressV1>),
380 ChannelCharityCampaignStartV1(Payload<channel::ChannelCharityCampaignStartV1>),
382 ChannelCharityCampaignStopV1(Payload<channel::ChannelCharityCampaignStopV1>),
384 #[deprecated(note = "use `Event::ChannelUpdateV2` instead")]
386 ChannelUpdateV1(Payload<channel::ChannelUpdateV1>),
387 ChannelUpdateV2(Payload<channel::ChannelUpdateV2>),
389 #[deprecated(note = "use `Event::ChannelFollowV2` instead")]
391 ChannelFollowV1(Payload<channel::ChannelFollowV1>),
392 ChannelFollowV2(Payload<channel::ChannelFollowV2>),
394 ChannelSubscribeV1(Payload<channel::ChannelSubscribeV1>),
396 ChannelCheerV1(Payload<channel::ChannelCheerV1>),
398 ChannelBanV1(Payload<channel::ChannelBanV1>),
400 ChannelUnbanV1(Payload<channel::ChannelUnbanV1>),
402 ChannelUnbanRequestCreateV1(Payload<channel::ChannelUnbanRequestCreateV1>),
404 ChannelUnbanRequestResolveV1(Payload<channel::ChannelUnbanRequestResolveV1>),
406 ChannelVipAddV1(Payload<channel::ChannelVipAddV1>),
408 ChannelVipRemoveV1(Payload<channel::ChannelVipRemoveV1>),
410 ChannelWarningAcknowledgeV1(Payload<channel::ChannelWarningAcknowledgeV1>),
412 ChannelWarningSendV1(Payload<channel::ChannelWarningSendV1>),
414 ChannelPointsAutomaticRewardRedemptionAddV1(
416 Payload<channel::ChannelPointsAutomaticRewardRedemptionAddV1>,
417 ),
418 ChannelPointsCustomRewardAddV1(Payload<channel::ChannelPointsCustomRewardAddV1>),
420 ChannelPointsCustomRewardUpdateV1(Payload<channel::ChannelPointsCustomRewardUpdateV1>),
422 ChannelPointsCustomRewardRemoveV1(Payload<channel::ChannelPointsCustomRewardRemoveV1>),
424 ChannelPointsCustomRewardRedemptionAddV1(
426 Payload<channel::ChannelPointsCustomRewardRedemptionAddV1>,
427 ),
428 ChannelPointsCustomRewardRedemptionUpdateV1(
430 Payload<channel::ChannelPointsCustomRewardRedemptionUpdateV1>,
431 ),
432 ChannelPollBeginV1(Payload<channel::ChannelPollBeginV1>),
434 ChannelPollProgressV1(Payload<channel::ChannelPollProgressV1>),
436 ChannelPollEndV1(Payload<channel::ChannelPollEndV1>),
438 ChannelPredictionBeginV1(Payload<channel::ChannelPredictionBeginV1>),
440 ChannelPredictionProgressV1(Payload<channel::ChannelPredictionProgressV1>),
442 ChannelPredictionLockV1(Payload<channel::ChannelPredictionLockV1>),
444 ChannelPredictionEndV1(Payload<channel::ChannelPredictionEndV1>),
446 ChannelRaidV1(Payload<channel::ChannelRaidV1>),
448 ChannelSharedChatBeginV1(Payload<channel::ChannelSharedChatBeginV1>),
450 ChannelSharedChatEndV1(Payload<channel::ChannelSharedChatEndV1>),
452 ChannelSharedChatUpdateV1(Payload<channel::ChannelSharedChatUpdateV1>),
454 ChannelShieldModeBeginV1(Payload<channel::ChannelShieldModeBeginV1>),
456 ChannelShieldModeEndV1(Payload<channel::ChannelShieldModeEndV1>),
458 ChannelShoutoutCreateV1(Payload<channel::ChannelShoutoutCreateV1>),
460 ChannelShoutoutReceiveV1(Payload<channel::ChannelShoutoutReceiveV1>),
462 ChannelSuspiciousUserMessageV1(Payload<channel::ChannelSuspiciousUserMessageV1>),
464 ChannelSuspiciousUserUpdateV1(Payload<channel::ChannelSuspiciousUserUpdateV1>),
466 ChannelGoalBeginV1(Payload<channel::ChannelGoalBeginV1>),
468 ChannelGoalProgressV1(Payload<channel::ChannelGoalProgressV1>),
470 ChannelGoalEndV1(Payload<channel::ChannelGoalEndV1>),
472 #[cfg(feature = "beta")]
474 ChannelGuestStarSessionBeginBeta(Payload<channel::ChannelGuestStarSessionBeginBeta>),
475 #[cfg(feature = "beta")]
477 ChannelGuestStarSessionEndBeta(Payload<channel::ChannelGuestStarSessionEndBeta>),
478 #[cfg(feature = "beta")]
480 ChannelGuestStarSettingsUpdateBeta(Payload<channel::ChannelGuestStarSettingsUpdateBeta>),
481 #[cfg(feature = "beta")]
483 ChannelGuestStarGuestUpdateBeta(Payload<channel::ChannelGuestStarGuestUpdateBeta>),
484 ChannelHypeTrainBeginV1(Payload<channel::ChannelHypeTrainBeginV1>),
486 ChannelHypeTrainProgressV1(Payload<channel::ChannelHypeTrainProgressV1>),
488 ChannelHypeTrainEndV1(Payload<channel::ChannelHypeTrainEndV1>),
490 ChannelModerateV1(Payload<channel::ChannelModerateV1>),
492 ChannelModerateV2(Payload<channel::ChannelModerateV2>),
494 ChannelModeratorAddV1(Payload<channel::ChannelModeratorAddV1>),
496 ChannelModeratorRemoveV1(Payload<channel::ChannelModeratorRemoveV1>),
498 ConduitShardDisabledV1(Payload<conduit::ConduitShardDisabledV1>),
500 StreamOnlineV1(Payload<stream::StreamOnlineV1>),
502 StreamOfflineV1(Payload<stream::StreamOfflineV1>),
504 UserUpdateV1(Payload<user::UserUpdateV1>),
506 UserAuthorizationGrantV1(Payload<user::UserAuthorizationGrantV1>),
508 UserAuthorizationRevokeV1(Payload<user::UserAuthorizationRevokeV1>),
510 UserWhisperMessageV1(Payload<user::UserWhisperMessageV1>),
512 ChannelSubscriptionEndV1(Payload<channel::ChannelSubscriptionEndV1>),
514 ChannelSubscriptionGiftV1(Payload<channel::ChannelSubscriptionGiftV1>),
516 ChannelSubscriptionMessageV1(Payload<channel::ChannelSubscriptionMessageV1>),
518}
519
520impl Event {
521 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 pub const fn is_notification(&self) -> bool { is_thing!(self, Notification) }
532
533 pub const fn is_revocation(&self) -> bool { is_thing!(self, Revocation) }
537
538 pub const fn is_verification_request(&self) -> bool { is_thing!(self, VerificationRequest) }
542
543 #[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 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; 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 #[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 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#[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 #[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 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#[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 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 #[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 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 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}