twitch_irc/message/twitch.rs
1//! Twitch-specifica that only appear on Twitch-specific messages/tags.
2
3use std::fmt::{Display, Formatter};
4use std::ops::Range;
5
6#[cfg(feature = "with-serde")]
7use {serde::Deserialize, serde::Serialize};
8
9/// Set of information describing the basic details of a Twitch user.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
12pub struct TwitchUserBasics {
13 /// The user's unique ID, e.g. `103973901`
14 pub id: String,
15 /// The user's login name. For many users, this is simply the lowercased version of their
16 /// (display) name, but there are also many users where there is no direct relation between
17 /// `login` and `name`.
18 ///
19 /// A Twitch user can change their `login` and `name` while still keeping their `id` constant.
20 /// For this reason, you should always prefer to use the `id` to uniquely identify a user, while
21 /// `login` and `name` are variable properties for them.
22 ///
23 /// The `login` name is used in many places to refer to users, e.g. in the URL for their channel page,
24 /// or also in almost all places on the Twitch IRC interface (e.g. when sending a message to a
25 /// channel, you specify the channel by its login name instead of ID).
26 pub login: String,
27 /// Display name of the user. When possible a user should be referred to using this name
28 /// in user-facing contexts.
29 ///
30 /// This value is never used to uniquely identify a user, and you
31 /// should avoid making assumptions about the format of this value.
32 /// For example, the `name` can contain non-ascii characters, it can contain spaces and
33 /// it can have spaces at the start and end (albeit rare).
34 pub name: String,
35}
36
37/// An RGB color, used to color chat user's names.
38///
39/// This struct's `Display` implementation formats the color in the way Twitch expects it for
40/// the "Update User Chat Color" API method, i.e. uppercase hex RGB with a `#`, e.g.:
41///
42/// ```rust
43/// use twitch_irc::message::RGBColor;
44/// let color = RGBColor {
45/// r: 0x12,
46/// g: 0x00,
47/// b: 0x0F
48/// };
49/// assert_eq!(color.to_string(), "#12000F");
50/// ```
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
53pub struct RGBColor {
54 /// Red component
55 pub r: u8,
56 /// Green component
57 pub g: u8,
58 /// Blue component
59 pub b: u8,
60}
61
62impl Display for RGBColor {
63 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64 write!(f, "#{:0>2X}{:0>2X}{:0>2X}", self.r, self.g, self.b)
65 }
66}
67
68/// A single emote, appearing as part of a message.
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
71pub struct Emote {
72 /// An ID identifying this emote. For example `25` for the "Kappa" emote, but can also be non-numeric,
73 /// for example on emotes modified using Twitch channel points, e.g.
74 /// `301512758_TK` for `pajaDent_TK` where `301512758` is the ID of the original `pajaDent` emote.
75 pub id: String,
76 /// A range of characters in the original message where the emote is placed.
77 ///
78 /// As is documented on `Range`, the `start` index of this range is inclusive, while the
79 /// `end` index is exclusive.
80 ///
81 /// This is always the exact range of characters that Twitch originally sent.
82 /// Note that due to [a Twitch bug](https://github.com/twitchdev/issues/issues/104)
83 /// (that this library intentionally works around), the character range specified here
84 /// might be out-of-bounds for the original message text string.
85 pub char_range: Range<usize>,
86 /// This is the text that this emote replaces, e.g. `Kappa` or `:)`.
87 pub code: String,
88}
89
90/// A single Twitch "badge" to be shown next to the user's name in chat.
91///
92/// The combination of `name` and `version` fully describes the exact badge to display.
93#[derive(Debug, Clone, PartialEq, Eq)]
94#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
95pub struct Badge {
96 /// A string identifying the type of badge. For example, `admin`, `moderator` or `subscriber`.
97 pub name: String,
98 /// A (usually) numeric version of this badge. Most badges only have one version (then usually
99 /// version will be `0` or `1`), but other types of badges have different versions (e.g. `subscriber`)
100 /// to differentiate between levels, or lengths, or similar, depending on the badge.
101 pub version: String,
102}
103
104/// If a message is sent in reply to another one, Twitch provides some basic information about the message
105/// that was replied to. It is optional, as not every message will be in reply to another message.
106#[derive(Debug, Clone, PartialEq, Eq)]
107#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
108pub struct ReplyParent {
109 /// Message UUID that this message is replying to.
110 pub message_id: String,
111 /// User that sent the message that is replying to.
112 pub reply_parent_user: TwitchUserBasics,
113 /// The text of the message that this message is replying to.
114 pub message_text: String,
115}
116
117/// Extract the `message_id` from a [`PrivmsgMessage`](crate::message::PrivmsgMessage) or directly
118/// use an arbitrary [`String`] or [`&str`] as a message ID. This trait allows you to plug both
119/// of these types directly into [`say_in_reply_to()`](crate::TwitchIRCClient::say_in_reply_to)
120/// for your convenience.
121///
122/// For tuples `(&str, &str)` or `(String, String)`, the first member is the login name
123/// of the channel the message was sent to, and the second member is the ID of the message
124/// to be deleted.
125///
126/// Note that even though [`UserNoticeMessage`](crate::message::UserNoticeMessage) has a
127/// `message_id`, you can NOT reply to these messages or delete them. For this reason,
128/// `ReplyToMessage` is not implemented for
129/// [`UserNoticeMessage`](crate::message::UserNoticeMessage).
130pub trait ReplyToMessage {
131 /// Login name of the channel that the message was sent to.
132 fn channel_login(&self) -> &str;
133 /// The unique string identifying the message, specified on the message via the `id` tag.
134 fn message_id(&self) -> &str;
135}
136
137impl<C, M> ReplyToMessage for (C, M)
138where
139 C: AsRef<str>,
140 M: AsRef<str>,
141{
142 fn channel_login(&self) -> &str {
143 self.0.as_ref()
144 }
145
146 fn message_id(&self) -> &str {
147 self.1.as_ref()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use crate::message::{IRCMessage, PrivmsgMessage, ReplyToMessage};
154 use std::convert::TryFrom;
155
156 #[test]
157 pub fn test_reply_to_message_trait_impl() {
158 // just making sure that DeleteMessage is implemented for all of these variants
159 let _a: Box<dyn ReplyToMessage> = Box::new(("asd", "def"));
160 let _b: Box<dyn ReplyToMessage> = Box::new(("asd".to_owned(), "def"));
161 let _c: Box<dyn ReplyToMessage> = Box::new(("asd", "def".to_owned()));
162 let d: Box<dyn ReplyToMessage> = Box::new(("asd".to_owned(), "def".to_owned()));
163
164 assert_eq!(d.channel_login(), "asd");
165 assert_eq!(d.message_id(), "def");
166 }
167
168 fn function_with_impl_arg(a: &impl ReplyToMessage) -> String {
169 a.message_id().to_owned()
170 }
171
172 #[test]
173 pub fn test_reply_to_message_trait_for_privmsg() {
174 let src = "@badge-info=;badges=;color=#0000FF;display-name=JuN1oRRRR;emotes=;flags=;id=e9d998c3-36f1-430f-89ec-6b887c28af36;mod=0;room-id=11148817;subscriber=0;tmi-sent-ts=1594545155039;turbo=0;user-id=29803735;user-type= :jun1orrrr!jun1orrrr@jun1orrrr.tmi.twitch.tv PRIVMSG #pajlada :dank cam";
175 let irc_message = IRCMessage::parse(src).unwrap();
176 let msg = PrivmsgMessage::try_from(irc_message).unwrap();
177
178 let msg_ref: &PrivmsgMessage = &msg; // making sure the trait is implemented for the ref as well
179 assert_eq!(msg_ref.channel_login(), "pajlada");
180 assert_eq!(msg_ref.message_id(), "e9d998c3-36f1-430f-89ec-6b887c28af36");
181 // testing references work as arguments, as intended
182 assert_eq!(
183 function_with_impl_arg(msg_ref),
184 "e9d998c3-36f1-430f-89ec-6b887c28af36"
185 );
186 }
187}