twitch_message/messages/
user_notice.rs1#![allow(deprecated)]
2
3use std::borrow::Cow;
4
5use crate::{parse_badges, Badge, Color, Emote, Tags};
6
7use super::{Message, UserType};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
12pub struct UserNotice<'a> {
13 pub raw: Cow<'a, str>,
15 pub tags: Tags<'a>,
17 pub channel: Cow<'a, str>,
19 pub data: Option<Cow<'a, str>>,
21}
22
23impl<'a> UserNotice<'a> {
24 pub fn badge_info<'t: 'a>(&'t self) -> impl Iterator<Item = Badge<'a>> + 't {
26 self.tags
27 .get("badge-info")
28 .into_iter()
29 .flat_map(parse_badges)
30 }
31
32 pub fn badges<'t: 'a>(&'t self) -> impl Iterator<Item = Badge<'a>> + 't {
34 Badge::from_tags(&self.tags)
35 }
36
37 pub fn emotes<'t: 'a>(&'t self) -> impl Iterator<Item = Emote<'a>> + 't {
39 self.data
40 .iter()
41 .flat_map(|data| Emote::from_tags(&self.tags, data))
42 }
43
44 pub fn color(&self) -> Option<Color> {
46 self.tags.color()
47 }
48
49 pub fn display_name(&self) -> Option<&str> {
51 self.tags.get("display-name")
52 }
53
54 pub fn system_msg(&self) -> Option<&str> {
56 self.tags.get("system-msg")
57 }
58
59 pub fn is_moderator(&self) -> bool {
61 self.tags.bool("mod")
62 }
63
64 pub fn is_subscriber(&self) -> bool {
66 self.tags.bool("subscriber")
67 }
68
69 pub fn is_turbo(&self) -> bool {
71 self.tags.bool("turbo")
72 }
73
74 pub fn login(&self) -> Option<&str> {
76 self.tags.get("login")
77 }
78
79 pub fn id(&self) -> Option<&str> {
81 self.tags.get("id")
82 }
83
84 pub fn msg_id(&self) -> Option<UserNoticeId> {
86 self.tags.get("msg-id").map(UserNoticeId::parse)
87 }
88
89 pub fn room_id(&self) -> Option<&str> {
91 self.tags.get("room_id")
92 }
93
94 pub fn user_id(&self) -> Option<&str> {
97 self.tags.get("user-id")
98 }
99
100 pub fn tmi_sent_ts(&self) -> Option<&str> {
102 self.tags.get("tmi-sent-ts")
103 }
104
105 pub fn user_type(&self) -> UserType {
107 self.tags
108 .get("user-type")
109 .map(UserType::parse)
110 .unwrap_or_default()
111 }
112
113 pub fn msg_param_cumulative_months(&self) -> Option<&str> {
117 self.tags.get("msg-param-cumulative-months")
118 }
119
120 pub fn msg_param_display_name(&self) -> Option<&str> {
124 self.tags
126 .get("msg-param-displayName")
127 .or_else(|| self.tags.get("msg-param-display-name"))
128 }
129
130 pub fn msg_param_login(&self) -> Option<&str> {
134 self.tags.get("msg-param-login")
135 }
136
137 pub fn msg_param_months(&self) -> Option<&str> {
145 self.tags.get("msg-param-months")
146 }
147
148 pub fn msg_param_promo_gift_total(&self) -> Option<&str> {
152 self.tags.get("msg-param-promo-gift-total")
153 }
154
155 pub fn msg_param_promo_name(&self) -> Option<&str> {
159 self.tags.get("msg-param-promo-name")
160 }
161
162 pub fn msg_param_recipient_display_name(&self) -> Option<&str> {
166 self.tags.get("msg-param-recipient-display-name")
167 }
168
169 pub fn msg_param_recipient_id(&self) -> Option<&str> {
173 self.tags.get("msg-param-recipient-id")
174 }
175
176 pub fn msg_param_recipient_user_name(&self) -> Option<&str> {
180 self.tags.get("msg-param-recipient-user-name")
181 }
182
183 pub fn msg_param_sender_login(&self) -> Option<&str> {
187 self.tags.get("msg-param-sender-login")
188 }
189
190 pub fn msg_param_sender_name(&self) -> Option<&str> {
194 self.tags.get("msg-param-sender-name")
195 }
196
197 pub fn msg_param_should_share_streak(&self) -> Option<&str> {
201 self.tags.get("msg-param-should-share-streak")
202 }
203
204 pub fn msg_param_streak_months(&self) -> Option<&str> {
208 self.tags.get("msg-param-streak-months")
209 }
210
211 pub fn msg_param_sub_plan(&self) -> Option<&str> {
215 self.tags.get("msg-param-sub-plan")
216 }
217
218 pub fn msg_param_sub_plan_name(&self) -> Option<&str> {
222 self.tags.get("msg-param-sub-plan-name")
223 }
224
225 pub fn msg_param_viewer_count(&self) -> Option<&str> {
229 self.tags.get("msg-param-viewerCount")
230 }
231
232 #[deprecated]
233 #[allow(missing_docs)]
234 pub fn msg_param_ritual_name(&self) -> Option<&str> {
235 self.tags.get("msg-param-ritual-name")
236 }
237
238 pub fn msg_param_threshold(&self) -> Option<&str> {
242 self.tags.get("msg-param-threshold")
243 }
244
245 pub fn msg_param_gift_months(&self) -> Option<&str> {
249 self.tags.get("msg-param-gift-months")
250 }
251
252 pub fn msg_param_domain(&self) -> Option<&str> {
256 self.tags.get("msg-param-domain")
257 }
258
259 pub fn msg_param_trigger_type(&self) -> Option<&str> {
263 self.tags.get("msg-param-trigger-type")
264 }
265
266 pub fn msg_param_selected_count(&self) -> Option<&str> {
270 self.tags.get("msg-param-selected-count")
271 }
272 pub fn msg_param_total_reward_count(&self) -> Option<&str> {
276 self.tags.get("msg-param-total-reward-count")
277 }
278 pub fn msg_param_trigger_amount(&self) -> Option<&str> {
282 self.tags.get("msg-param-trigger-amount")
283 }
284}
285
286#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
288#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
289pub enum UserNoticeId {
290 Sub,
292 Resub,
294 Subgift,
296 SubMysteryGift,
298 GiftPaidUpgrade,
300 RewardGift,
302 AnonGiftPaidUpgrade,
304 Raid,
306 Unraid,
308 #[allow(missing_docs)]
309 #[deprecated]
310 Ritual,
311 BitsBadgeTier,
313 #[default]
315 Unknown,
316}
317
318impl UserNoticeId {
319 fn parse(input: &str) -> Self {
320 match input {
321 "sub" => Self::Sub,
322 "resub" => Self::Resub,
323 "subgift" => Self::Subgift,
324 "submysterygift" => Self::SubMysteryGift,
325 "giftpaidupgrade" => Self::GiftPaidUpgrade,
326 "rewardgift" => Self::RewardGift,
327 "anongiftpaidupgrade" => Self::AnonGiftPaidUpgrade,
328 "raid" => Self::Raid,
329 "unraid" => Self::Unraid,
330 "ritual" => Self::Ritual,
331 "bitsbadgetier" => Self::BitsBadgeTier,
332 _ => Self::Unknown,
333 }
334 }
335}
336
337impl UserNotice<'_> {
338 fn validate(value: &Message<'_>) -> bool {
339 !value.args.is_empty()
340 }
341}
342
343impl<'a> TryFrom<Message<'a>> for UserNotice<'a> {
344 type Error = Message<'a>;
345
346 fn try_from(mut value: Message<'a>) -> Result<Self, Self::Error> {
347 if !Self::validate(&value) {
348 return Err(value);
349 }
350
351 Ok(Self {
352 raw: value.raw,
353 tags: value.tags,
354 channel: value.args.remove(0),
355 data: value.data,
356 })
357 }
358}
359
360impl<'a, 'b> TryFrom<&'b Message<'a>> for UserNotice<'a> {
361 type Error = &'b Message<'a>;
362
363 fn try_from(value: &'b Message<'a>) -> Result<Self, Self::Error> {
364 if !Self::validate(value) {
365 return Err(value);
366 }
367
368 Ok(Self {
369 raw: value.raw.clone(),
370 tags: value.tags.clone(),
371 channel: value.args[0].clone(),
372 data: value.data.clone(),
373 })
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use crate::test_util;
381
382 #[test]
383 fn user_notice() {
384 let inputs = [
385 "@badge-info=;badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=resub;msg-param-cumulative-months=6;msg-param-streak-months=2;msg-param-should-share-streak=1;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=12345678;subscriber=1;system-msg=ronni\\shas\\ssubscribed\\sfor\\s6\\smonths!;tmi-sent-ts=1507246572675;turbo=1;user-id=87654321;user-type=staff :tmi.twitch.tv USERNOTICE #dallas :Great stream -- keep it up!\r\n",
386 "@badge-info=;badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=55554444;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\\sof\\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=87654321;user-type=staff :tmi.twitch.tv USERNOTICE #forstycup\r\n",
387 "@badge-info=;badges=turbo/1;color=#9ACD32;display-name=TestChannel;emotes=;id=3d830f12-795c-447d-af3c-ea05e40fbddb;login=testchannel;mod=0;msg-id=raid;msg-param-displayName=TestChannel;msg-param-login=testchannel;msg-param-viewerCount=15;room-id=33332222;subscriber=0;system-msg=15\\sraiders\\sfrom\\sTestChannel\\shave\\sjoined\\n!;tmi-sent-ts=1507246572675;turbo=1;user-id=123456;user-type= :tmi.twitch.tv USERNOTICE #othertestchannel\r\n",
388 "@badge-info=;badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=ritual;msg-param-ritual-name=new_chatter;room-id=87654321;subscriber=0;system-msg=Seventoes\\sis\\snew\\shere!;tmi-sent-ts=1508363903826;turbo=0;user-id=77776666;user-type= :tmi.twitch.tv USERNOTICE #seventoes :HeyGuys\r\n",
389 ];
390
391 for (input, (channel, data)) in inputs.into_iter().zip([
392 ("#dallas", Some("Great stream -- keep it up!")),
393 ("#forstycup", None),
394 ("#othertestchannel", None),
395 ("#seventoes", Some("HeyGuys")),
396 ]) {
397 let (raw, tags) = test_util::raw_and_tags(input);
398 assert_eq!(
399 crate::test_util::parse_as::<UserNotice>(input),
400 UserNotice {
401 raw,
402 tags,
403 channel: Cow::from(channel),
404 data: data.map(Cow::from)
405 }
406 );
407 }
408 }
409}