Skip to main content

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}