From 685c8d7971b410f8b80bb434401fe88341bebf15 Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:54:40 +0300 Subject: [PATCH 1/8] replace strings everywhere with FastStr --- Cargo.toml | 1 + src/client/event_loop.rs | 20 +-- src/client/mod.rs | 52 +++++--- src/client/pool_connection.rs | 5 +- src/message/commands/clearchat.rs | 50 ++++--- src/message/commands/clearmsg.rs | 29 ++-- src/message/commands/globaluserstate.rs | 23 ++-- src/message/commands/join.rs | 18 ++- src/message/commands/mod.rs | 83 +++++++----- src/message/commands/privmsg.rs | 43 +++--- src/message/commands/usernotice.rs | 170 +++++++++++++----------- src/message/commands/userstate.rs | 23 ++-- src/message/commands/whisper.rs | 27 ++-- src/message/mod.rs | 48 ++++--- src/message/twitch.rs | 74 ++++++++--- src/transport/tcp.rs | 4 +- 16 files changed, 406 insertions(+), 264 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f73a9a..ccf16bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ bytes = { version = "1", optional = true } chrono = { version = "0.4", default-features = false } either = "1" enum_dispatch = "0.3" +fast-str = { version = "1.0.0", features = ["stack", "serde"] } futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } prometheus = { version = "0.13", default-features = false, optional = true } reqwest = { version = "0.11", default-features = false, features = ["json"], optional = true } diff --git a/src/client/event_loop.rs b/src/client/event_loop.rs index 90db44c..10ae5fd 100644 --- a/src/client/event_loop.rs +++ b/src/client/event_loop.rs @@ -12,6 +12,7 @@ use crate::message::{IRCMessage, JoinMessage, PartMessage}; #[cfg(feature = "metrics-collection")] use crate::metrics::MetricsBundle; use crate::transport::Transport; +use fast_str::FastStr; use std::collections::{HashSet, VecDeque}; use std::sync::{Arc, Weak}; use tokio::sync::{mpsc, oneshot}; @@ -27,17 +28,17 @@ pub(crate) enum ClientLoopCommand { return_sender: oneshot::Sender>>, }, Join { - channel_login: String, + channel_login: FastStr, }, GetChannelStatus { - channel_login: String, + channel_login: FastStr, return_sender: oneshot::Sender<(bool, bool)>, }, Part { - channel_login: String, + channel_login: FastStr, }, SetWantedChannels { - channels: HashSet, + channels: HashSet, }, Ping { return_sender: oneshot::Sender>>, @@ -248,7 +249,7 @@ impl ClientLoopWorker { /// /// The client will make best attempts to stay joined to this channel. I/O errors will be /// compensated by retrying the join process. For this reason, this method returns no error. - fn join(&mut self, channel_login: String) { + fn join(&mut self, channel_login: FastStr) { let channel_already_confirmed_joined = self.connections.iter().any(|c| { c.wanted_channels.contains(&channel_login) && c.server_channels.contains(&channel_login) }); @@ -295,7 +296,7 @@ impl ClientLoopWorker { self.update_metrics(); } - fn set_wanted_channels(&mut self, channels: HashSet) { + fn set_wanted_channels(&mut self, channels: HashSet) { // part channels as needed self.connections .iter() @@ -312,7 +313,7 @@ impl ClientLoopWorker { } } - fn get_channel_status(&mut self, channel_login: String) -> (bool, bool) { + fn get_channel_status(&mut self, channel_login: FastStr) -> (bool, bool) { let wanted = self .connections .iter() @@ -324,7 +325,7 @@ impl ClientLoopWorker { (wanted, joined_on_server) } - fn part(&mut self, channel_login: String) { + fn part(&mut self, channel_login: FastStr) { // skip the PART altogether if the last message we sent regarding that channel was a PART // (or nothing at all, for that matter). if self @@ -419,7 +420,8 @@ impl ClientLoopWorker { .iter_mut() .find(|c| c.id == source_connection_id) .unwrap(); - c.server_channels.remove(channel_login); + let channel_login = FastStr::from_ref(channel_login); + c.server_channels.remove::(&channel_login); // update metrics about channel numbers self.update_metrics(); diff --git a/src/client/mod.rs b/src/client/mod.rs index 870d8af..bc42565 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -15,6 +15,7 @@ use crate::metrics::MetricsBundle; use crate::transport::Transport; use crate::validate::validate_login; use crate::{irc, validate}; +use fast_str::FastStr; use std::collections::HashSet; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; @@ -121,7 +122,11 @@ impl TwitchIRCClient { /// /// If you want to just send a normal chat message, `say()` should be preferred since it /// prevents commands like `/ban` from accidentally being executed. - pub async fn privmsg(&self, channel_login: String, message: String) -> Result<(), Error> { + pub async fn privmsg( + &self, + channel_login: FastStr, + message: FastStr, + ) -> Result<(), Error> { self.send_message(irc!["PRIVMSG", format!("#{}", channel_login), message]) .await } @@ -135,8 +140,12 @@ impl TwitchIRCClient { /// No particular filtering is performed on the message. If the message is too long for chat, /// it will not be cut short or split into multiple messages (what happens is determined /// by the behaviour of the Twitch IRC server). - pub async fn say(&self, channel_login: String, message: String) -> Result<(), Error> { - self.privmsg(channel_login, format!(". {}", message)).await + pub async fn say(&self, channel_login: FastStr, message: FastStr) -> Result<(), Error> { + self.privmsg( + channel_login, + FastStr::from_string(format!(". {}", message)), + ) + .await } /// Say a `/me` chat message in the given Twitch channel. These messages are usually @@ -160,9 +169,12 @@ impl TwitchIRCClient { /// No particular filtering is performed on the message. If the message is too long for chat, /// it will not be cut short or split into multiple messages (what happens is determined /// by the behaviour of the Twitch IRC server). - pub async fn me(&self, channel_login: String, message: String) -> Result<(), Error> { - self.privmsg(channel_login, format!("/me {}", message)) - .await + pub async fn me(&self, channel_login: FastStr, message: FastStr) -> Result<(), Error> { + self.privmsg( + channel_login, + FastStr::from_string(format!("/me {}", message)), + ) + .await } /// Reply to a given message. The sent message is tagged to be in reply of the @@ -181,7 +193,7 @@ impl TwitchIRCClient { /// be one of the following: /// /// * a [`&PrivmsgMessage`](crate::message::PrivmsgMessage) - /// * a tuple `(&str, &str)` or `(String, String)`, where the first member is the login name + /// * a tuple `(&str, &str)` or `(FastStr, FastStr)`, where the first member is the login name /// of the channel the message was sent to, and the second member is the ID of the message /// to reply to. /// @@ -192,7 +204,7 @@ impl TwitchIRCClient { pub async fn say_in_reply_to( &self, reply_to: &impl ReplyToMessage, - message: String, + message: FastStr, ) -> Result<(), Error> { self.say_or_me_in_reply_to(reply_to, message, false).await } @@ -216,7 +228,7 @@ impl TwitchIRCClient { /// be one of the following: /// /// * a [`&PrivmsgMessage`](crate::message::PrivmsgMessage) - /// * a tuple `(&str, &str)` or `(String, String)`, where the first member is the login name + /// * a tuple `(&str, &str)` or `(FastStr, FastStr)`, where the first member is the login name /// of the channel the message was sent to, and the second member is the ID of the message /// to reply to. /// @@ -227,7 +239,7 @@ impl TwitchIRCClient { pub async fn me_in_reply_to( &self, reply_to: &impl ReplyToMessage, - message: String, + message: FastStr, ) -> Result<(), Error> { self.say_or_me_in_reply_to(reply_to, message, true).await } @@ -235,7 +247,7 @@ impl TwitchIRCClient { async fn say_or_me_in_reply_to( &self, reply_to: &impl ReplyToMessage, - message: String, + message: FastStr, me: bool, ) -> Result<(), Error> { let mut tags = IRCTags::new(); @@ -247,10 +259,10 @@ impl TwitchIRCClient { let irc_message = IRCMessage::new( tags, None, - "PRIVMSG".to_owned(), + FastStr::from_static("PRIVMSG"), vec![ - format!("#{}", reply_to.channel_login()), - format!("{} {}", if me { "/me" } else { "." }, message), + FastStr::from_string(format!("#{}", reply_to.channel_login())), + FastStr::from_string(format!("{} {}", if me { "/me" } else { "." }, message)), ], // The prefixed "." prevents commands from being executed if not in /me-mode ); self.send_message(irc_message).await @@ -291,7 +303,7 @@ impl TwitchIRCClient { /// /// Returns a [validate::Error] if the passed `channel_login` is of /// [invalid format](crate::validate::validate_login). Returns `Ok(())` otherwise. - pub fn join(&self, channel_login: String) -> Result<(), validate::Error> { + pub fn join(&self, channel_login: FastStr) -> Result<(), validate::Error> { validate_login(&channel_login)?; self.client_loop_tx @@ -309,7 +321,7 @@ impl TwitchIRCClient { /// /// Returns a [validate::Error] if the passed `channel_login` is of /// [invalid format](crate::validate::validate_login). Returns `Ok(())` otherwise. - pub fn set_wanted_channels(&self, channels: HashSet) -> Result<(), validate::Error> { + pub fn set_wanted_channels(&self, channels: HashSet) -> Result<(), validate::Error> { for channel_login in channels.iter() { validate_login(channel_login)?; } @@ -344,8 +356,8 @@ impl TwitchIRCClient { /// /// `(false, false)` is returned for a channel that has not been joined previously at all /// or where a previous `PART` command has completed. - pub async fn get_channel_status(&self, channel_login: String) -> (bool, bool) { - // channel_login format sanity check not really needed here, the code will deal with arbitrary strings just fine + pub async fn get_channel_status(&self, channel_login: FastStr) -> (bool, bool) { + // channel_login format sanity check not really needed here, the code will deal with arbitrary FastStrs just fine let (return_tx, return_rx) = oneshot::channel(); self.client_loop_tx @@ -362,8 +374,8 @@ impl TwitchIRCClient { /// /// This has the same semantics as `join()`. Similarly, a `part()` call will have no effect /// if the channel is not currently joined. - pub fn part(&self, channel_login: String) { - // channel_login format sanity check not really needed here, the code will deal with arbitrary strings just fine + pub fn part(&self, channel_login: FastStr) { + // channel_login format sanity check not really needed here, the code will deal with arbitrary FastStrs just fine self.client_loop_tx .send(ClientLoopCommand::Part { channel_login }) diff --git a/src/client/pool_connection.rs b/src/client/pool_connection.rs index af5a53a..f67f518 100644 --- a/src/client/pool_connection.rs +++ b/src/client/pool_connection.rs @@ -2,6 +2,7 @@ use crate::config::ClientConfig; use crate::connection::Connection; use crate::login::LoginCredentials; use crate::transport::Transport; +use fast_str::FastStr; use std::collections::{HashSet, VecDeque}; use std::sync::Arc; use std::time::Instant; @@ -30,9 +31,9 @@ pub(crate) struct PoolConnection { /// The connection handle that this is wrapping pub connection: Arc>, /// see the documentation on `TwitchIRCClient` for what `wanted_channels` and `server_channels` mean - pub wanted_channels: HashSet, + pub wanted_channels: HashSet, /// see the documentation on `TwitchIRCClient` for what `wanted_channels` and `server_channels` mean - pub server_channels: HashSet, + pub server_channels: HashSet, /// this has a list of times when messages were sent out on this pool connection, /// at the front there will be the oldest, and at the back the newest entries pub message_send_times: VecDeque, diff --git a/src/message/commands/clearchat.rs b/src/message/commands/clearchat.rs index 8f59423..63f9954 100644 --- a/src/message/commands/clearchat.rs +++ b/src/message/commands/clearchat.rs @@ -1,7 +1,7 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; -use std::convert::TryFrom; +use fast_str::FastStr; use std::str::FromStr; use std::time::Duration; @@ -12,12 +12,18 @@ use {serde::Deserialize, serde::Serialize}; /// /// This represents the `CLEARCHAT` IRC command. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct ClearChatMessage { /// Login name of the channel that this message was sent to - pub channel_login: String, + pub channel_login: FastStr, /// ID of the channel that this message was sent to - pub channel_id: String, + pub channel_id: FastStr, /// The action that this `CLEARCHAT` message encodes - one of Timeout, Permaban, and the /// chat being cleared. See `ClearChatAction` for details pub action: ClearChatAction, @@ -30,23 +36,29 @@ pub struct ClearChatMessage { /// One of the three types of meaning a `CLEARCHAT` message can have. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub enum ClearChatAction { /// A moderator cleared the entire chat. ChatCleared, /// A user was permanently banned. UserBanned { /// Login name of the user that was banned - user_login: String, + user_login: FastStr, /// ID of the user that was banned - user_id: String, + user_id: FastStr, }, /// A user was temporarily banned (timed out). UserTimedOut { /// Login name of the user that was banned - user_login: String, + user_login: FastStr, /// ID of the user that was banned - user_id: String, + user_id: FastStr, /// Duration that the user was timed out for. timeout_length: Duration, }, @@ -70,28 +82,30 @@ impl TryFrom for ClearChatMessage { let action = match source.params.get(1) { Some(user_login) => { // ban or timeout - let user_id = source.try_get_nonempty_tag_value("target-user-id")?; + let user_id = + FastStr::from_ref(source.try_get_nonempty_tag_value("target-user-id")?); let ban_duration = source.try_get_optional_nonempty_tag_value("ban-duration")?; match ban_duration { Some(ban_duration) => { let ban_duration = u64::from_str(ban_duration).map_err(|_| { + let ban_duration = FastStr::from_ref(ban_duration); ServerMessageParseError::MalformedTagValue( - source.to_owned(), + source.clone(), "ban-duration", - ban_duration.to_owned(), + ban_duration, ) })?; ClearChatAction::UserTimedOut { - user_login: user_login.to_owned(), - user_id: user_id.to_owned(), + user_login: user_login.clone(), // Clone allowed because it's params. FastStr may turn it into Arc. + user_id: user_id, timeout_length: Duration::from_secs(ban_duration), } } None => ClearChatAction::UserBanned { - user_login: user_login.to_owned(), - user_id: user_id.to_owned(), + user_login: user_login.clone(), + user_id: user_id.clone(), }, } } @@ -99,8 +113,8 @@ impl TryFrom for ClearChatMessage { }; Ok(ClearChatMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + channel_id: FastStr::from_ref(source.try_get_nonempty_tag_value("room-id")?), action, server_timestamp: source.try_get_timestamp("tmi-sent-ts")?, source, diff --git a/src/message/commands/clearmsg.rs b/src/message/commands/clearmsg.rs index 0245c16..07dca9a 100644 --- a/src/message/commands/clearmsg.rs +++ b/src/message/commands/clearmsg.rs @@ -1,6 +1,7 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; +use fast_str::FastStr; use std::convert::TryFrom; #[cfg(feature = "with-serde")] @@ -10,18 +11,24 @@ use {serde::Deserialize, serde::Serialize}; /// /// The deleted message is identified by its `message_id`. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct ClearMsgMessage { /// Login name of the channel that the deleted message was posted in. - pub channel_login: String, - // pub channel_id: String, + pub channel_login: FastStr, + // pub channel_id: FastStr, /// login name of the user that sent the original message that was deleted by this /// `CLEARMSG`. - pub sender_login: String, + pub sender_login: FastStr, /// ID of the message that was deleted. - pub message_id: String, + pub message_id: FastStr, /// Text of the message that was deleted - pub message_text: String, + pub message_text: FastStr, /// Whether the deleted message was an action (`/me`) pub is_action: bool, /// server timestamp for the time when the delete command was executed. @@ -46,14 +53,12 @@ impl TryFrom for ClearMsgMessage { let (message_text, is_action) = source.try_get_message_text()?; Ok(ClearMsgMessage { - channel_login: source.try_get_channel_login()?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), // channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), - sender_login: source.try_get_nonempty_tag_value("login")?.to_owned(), - message_id: source - .try_get_nonempty_tag_value("target-msg-id")? - .to_owned(), + sender_login: FastStr::from_ref(source.try_get_nonempty_tag_value("login")?), + message_id: FastStr::from_ref(source.try_get_nonempty_tag_value("target-msg-id")?), server_timestamp: source.try_get_timestamp("tmi-sent-ts")?, - message_text: message_text.to_owned(), + message_text: FastStr::from_ref(message_text), is_action, source, }) diff --git a/src/message/commands/globaluserstate.rs b/src/message/commands/globaluserstate.rs index d5198f2..72e2c0b 100644 --- a/src/message/commands/globaluserstate.rs +++ b/src/message/commands/globaluserstate.rs @@ -1,8 +1,9 @@ +use fast_str::FastStr; + use crate::message::commands::IRCMessageParseExt; use crate::message::twitch::{Badge, RGBColor}; use crate::message::{IRCMessage, ServerMessageParseError}; use std::collections::HashSet; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; @@ -11,12 +12,18 @@ use {serde::Deserialize, serde::Serialize}; /// /// This message is not sent if you log into chat as an anonymous user. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct GlobalUserStateMessage { /// ID of the logged in user - pub user_id: String, + pub user_id: FastStr, /// Name (also called display name) of the logged in user - pub user_name: String, + pub user_name: FastStr, /// Metadata related to the chat badges in the `badges` tag. /// /// Currently this is used only for `subscriber`, to indicate the exact number of months @@ -30,7 +37,7 @@ pub struct GlobalUserStateMessage { /// List of badges the logged in user has in all channels. pub badges: Vec, /// List of emote set IDs the logged in user has available. This always contains at least one entry ("0"). - pub emote_sets: HashSet, + pub emote_sets: HashSet, /// What name color the logged in user has chosen. The same color is used in all channels. pub name_color: Option, @@ -50,10 +57,8 @@ impl TryFrom for GlobalUserStateMessage { // @badge-info=;badges=;color=#19E6E6;display-name=randers;emote-sets=0,42,237,4236,15961,19194,771823,1511293,1641460,1641461,1641462,300206295,300374282,300432482,300548756,472873131,477339272,488737509,537206155,625908879;user-id=40286300;user-type= :tmi.twitch.tv GLOBALUSERSTATE Ok(GlobalUserStateMessage { - user_id: source.try_get_nonempty_tag_value("user-id")?.to_owned(), - user_name: source - .try_get_nonempty_tag_value("display-name")? - .to_owned(), + user_id: FastStr::from_ref(source.try_get_nonempty_tag_value("user-id")?), + user_name: FastStr::from_ref(source.try_get_nonempty_tag_value("display-name")?), badge_info: source.try_get_badges("badge-info")?, badges: source.try_get_badges("badges")?, emote_sets: source.try_get_emote_sets("emote-sets")?, diff --git a/src/message/commands/join.rs b/src/message/commands/join.rs index e0dd771..3a77161 100644 --- a/src/message/commands/join.rs +++ b/src/message/commands/join.rs @@ -1,3 +1,5 @@ +use fast_str::FastStr; + use crate::message::commands::{IRCMessageParseExt, ServerMessageParseError}; use crate::message::IRCMessage; use std::convert::TryFrom; @@ -7,13 +9,19 @@ use {serde::Deserialize, serde::Serialize}; /// Message received when you successfully join a channel. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct JoinMessage { /// Login name of the channel you joined. - pub channel_login: String, + pub channel_login: FastStr, /// The login name of the logged in user (the login name of the user that joined the channel, /// which is the logged in user). - pub user_login: String, + pub user_login: FastStr, /// The message that this `JoinMessage` was parsed from. pub source: IRCMessage, @@ -28,8 +36,8 @@ impl TryFrom for JoinMessage { } Ok(JoinMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - user_login: source.try_get_prefix_nickname()?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + user_login: FastStr::from_ref(source.try_get_prefix_nickname()?), source, }) } diff --git a/src/message/commands/mod.rs b/src/message/commands/mod.rs index c152744..649c3fb 100644 --- a/src/message/commands/mod.rs +++ b/src/message/commands/mod.rs @@ -28,6 +28,7 @@ use crate::message::{ ReplyParent, RoomStateMessage, TwitchUserBasics, UserNoticeMessage, WhisperMessage, }; use chrono::{DateTime, TimeZone, Utc}; +use fast_str::FastStr; use std::collections::HashSet; use std::convert::TryFrom; use std::ops::Range; @@ -56,7 +57,7 @@ pub enum ServerMessageParseError { MissingTagValue(IRCMessage, &'static str), /// Malformed tag value for tag `key`, value was `value` #[error("Could not parse IRC message {} as ServerMessage: Malformed tag value for tag `{1}`, value was `{2}`", .0.as_raw_irc())] - MalformedTagValue(IRCMessage, &'static str, String), + MalformedTagValue(IRCMessage, &'static str, FastStr), /// No parameter found at index `n` #[error("Could not parse IRC message {} as ServerMessage: No parameter found at index {1}", .0.as_raw_irc())] MissingParameter(IRCMessage, usize), @@ -113,7 +114,7 @@ trait IRCMessageParseExt { fn try_get_emote_sets( &self, tag_key: &'static str, - ) -> Result, ServerMessageParseError>; + ) -> Result, ServerMessageParseError>; fn try_get_badges(&self, tag_key: &'static str) -> Result, ServerMessageParseError>; fn try_get_color( &self, @@ -244,7 +245,8 @@ impl IRCMessageParseExt for IRCMessage { let mut emotes = Vec::new(); - let make_error = || MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()); + let make_error = + || MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)); // emotes tag format: // emote_id:from-to,from-to,from-to/emote_id:from-to,from-to/emote_id:from-to @@ -266,14 +268,14 @@ impl IRCMessageParseExt for IRCMessage { .chars() .skip(start) .take(code_length) - .collect::(); + .collect::(); // we intentionally gracefully handle indices that are out of bounds for the - // given string by taking as much as possible until the end of the string. + // given FastStr by taking as much as possible until the end of the FastStr. // This is to work around a Twitch bug: https://github.com/twitchdev/issues/issues/104 emotes.push(Emote { - id: emote_id.to_owned(), + id: FastStr::from_ref(emote_id), char_range: Range { start, end }, code, }); @@ -288,13 +290,13 @@ impl IRCMessageParseExt for IRCMessage { fn try_get_emote_sets( &self, tag_key: &'static str, - ) -> Result, ServerMessageParseError> { + ) -> Result, ServerMessageParseError> { let src = self.try_get_tag_value(tag_key)?; if src.is_empty() { Ok(HashSet::new()) } else { - Ok(src.split(',').map(|s| s.to_owned()).collect()) + Ok(src.split(',').map(|s| FastStr::from_ref(s)).collect()) } } @@ -308,7 +310,8 @@ impl IRCMessageParseExt for IRCMessage { let mut badges = Vec::new(); - let make_error = || MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()); + let make_error = + || MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)); // badges tag format: // admin/1,moderator/1,subscriber/12 @@ -316,8 +319,8 @@ impl IRCMessageParseExt for IRCMessage { let (name, version) = src.split_once('/').ok_or_else(make_error)?; badges.push(Badge { - name: name.to_owned(), - version: version.to_owned(), + name: FastStr::from_ref(name), + version: FastStr::from_ref(version), }); } @@ -329,7 +332,8 @@ impl IRCMessageParseExt for IRCMessage { tag_key: &'static str, ) -> Result, ServerMessageParseError> { let tag_value = self.try_get_tag_value(tag_key)?; - let make_error = || MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()); + let make_error = + || MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)); if tag_value.is_empty() { return Ok(None); @@ -352,8 +356,9 @@ impl IRCMessageParseExt for IRCMessage { tag_key: &'static str, ) -> Result { let tag_value = self.try_get_nonempty_tag_value(tag_key)?; - let number = N::from_str(tag_value) - .map_err(|_| MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()))?; + let number = N::from_str(tag_value).map_err(|_| { + MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)) + })?; Ok(number) } @@ -373,8 +378,9 @@ impl IRCMessageParseExt for IRCMessage { None => return Ok(None), }; - let number = N::from_str(tag_value) - .map_err(|_| MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()))?; + let number = N::from_str(tag_value).map_err(|_| { + MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)) + })?; Ok(Some(number)) } @@ -391,11 +397,14 @@ impl IRCMessageParseExt for IRCMessage { ) -> Result, ServerMessageParseError> { // e.g. tmi-sent-ts. let tag_value = self.try_get_nonempty_tag_value(tag_key)?; - let milliseconds_since_epoch = i64::from_str(tag_value) - .map_err(|_| MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned()))?; + let milliseconds_since_epoch = i64::from_str(tag_value).map_err(|_| { + MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)) + })?; Utc.timestamp_millis_opt(milliseconds_since_epoch) .single() - .ok_or_else(|| MalformedTagValue(self.to_owned(), tag_key, tag_value.to_owned())) + .ok_or_else(|| { + MalformedTagValue(self.to_owned(), tag_key, FastStr::from_ref(tag_value)) + }) } fn try_get_optional_reply_parent( @@ -407,19 +416,17 @@ impl IRCMessageParseExt for IRCMessage { } Ok(Some(ReplyParent { - message_id: self.try_get_tag_value("reply-parent-msg-id")?.to_owned(), + message_id: FastStr::from_ref(self.try_get_tag_value("reply-parent-msg-id")?), reply_parent_user: TwitchUserBasics { - id: self - .try_get_nonempty_tag_value("reply-parent-user-id")? - .to_owned(), - login: self - .try_get_nonempty_tag_value("reply-parent-user-login")? - .to_owned(), - name: self - .try_get_nonempty_tag_value("reply-parent-display-name")? - .to_owned(), + id: FastStr::from_ref(self.try_get_nonempty_tag_value("reply-parent-user-id")?), + login: FastStr::from_ref( + self.try_get_nonempty_tag_value("reply-parent-user-login")?, + ), + name: FastStr::from_ref( + self.try_get_nonempty_tag_value("reply-parent-display-name")?, + ), }, - message_text: self.try_get_tag_value("reply-parent-msg-body")?.to_owned(), + message_text: FastStr::from_ref(self.try_get_tag_value("reply-parent-msg-body")?), })) } } @@ -431,7 +438,13 @@ impl IRCMessageParseExt for IRCMessage { // which combined with #[non_exhaustive] allows us to add enum variants // without making a major release #[derive(Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] #[doc(hidden)] pub struct HiddenIRCMessage(pub(self) IRCMessage); @@ -469,7 +482,13 @@ pub struct HiddenIRCMessage(pub(self) IRCMessage); /// } /// ``` #[derive(Debug, Clone)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] #[non_exhaustive] pub enum ServerMessage { /// `CLEARCHAT` message diff --git a/src/message/commands/privmsg.rs b/src/message/commands/privmsg.rs index 0ffb176..3b19636 100644 --- a/src/message/commands/privmsg.rs +++ b/src/message/commands/privmsg.rs @@ -2,6 +2,7 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics}; use crate::message::{IRCMessage, ReplyParent, ReplyToMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; +use fast_str::FastStr; use std::convert::TryFrom; #[cfg(feature = "with-serde")] @@ -9,14 +10,20 @@ use {serde::Deserialize, serde::Serialize}; /// A regular Twitch chat message. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct PrivmsgMessage { /// Login name of the channel that the message was sent to. - pub channel_login: String, + pub channel_login: FastStr, /// ID of the channel that the message was sent to. - pub channel_id: String, + pub channel_id: FastStr, /// The message text that was sent. - pub message_text: String, + pub message_text: FastStr, /// Optional reply parent of the message, containing data about the message that this message is replying to. pub reply_parent: Option, /// Whether this message was made using the `/me` command. @@ -48,9 +55,9 @@ pub struct PrivmsgMessage { /// A list of emotes in this message. Each emote replaces a part of the `message_text`. /// These emotes are sorted in the order that they appear in the message. pub emotes: Vec, - /// A string uniquely identifying this message. Can be used with the Twitch API to + /// A FastStr uniquely identifying this message. Can be used with the Twitch API to /// delete single messages. See also the `CLEARMSG` message type. - pub message_id: String, + pub message_id: FastStr, /// Timestamp of when this message was sent. pub server_timestamp: DateTime, @@ -69,14 +76,12 @@ impl TryFrom for PrivmsgMessage { let (message_text, is_action) = source.try_get_message_text()?; Ok(PrivmsgMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + channel_id: FastStr::from_ref(source.try_get_nonempty_tag_value("room-id")?), sender: TwitchUserBasics { - id: source.try_get_nonempty_tag_value("user-id")?.to_owned(), - login: source.try_get_prefix_nickname()?.to_owned(), - name: source - .try_get_nonempty_tag_value("display-name")? - .to_owned(), + id: FastStr::from_ref(source.try_get_nonempty_tag_value("user-id")?), + login: FastStr::from_ref(source.try_get_prefix_nickname()?), + name: FastStr::from_ref(source.try_get_nonempty_tag_value("display-name")?), }, badge_info: source.try_get_badges("badge-info")?, badges: source.try_get_badges("badges")?, @@ -84,8 +89,8 @@ impl TryFrom for PrivmsgMessage { name_color: source.try_get_color("color")?, emotes: source.try_get_emotes("emotes", message_text)?, server_timestamp: source.try_get_timestamp("tmi-sent-ts")?, - message_id: source.try_get_nonempty_tag_value("id")?.to_owned(), - message_text: message_text.to_owned(), + message_id: FastStr::from_ref(source.try_get_nonempty_tag_value("id")?), + message_text: FastStr::from_ref(message_text), reply_parent: source.try_get_optional_reply_parent()?, is_action, source, @@ -262,7 +267,7 @@ mod tests { message_id: "6b13e51b-7ecb-43b5-ba5b-2bb5288df696".to_owned(), reply_parent_user: TwitchUserBasics { id: "37940952".to_owned(), - login: "retoon".to_string(), + login: "retoon".to_FastStr(), name: "Retoon".to_owned(), }, message_text: "hello".to_owned() @@ -374,7 +379,7 @@ mod tests { #[test] fn test_emote_after_emoji() { // emojis are wider than one byte, tests that indices correctly refer - // to unicode scalar values, and not bytes in the utf-8 string + // to unicode scalar values, and not bytes in the utf-8 FastStr let src = "@badge-info=subscriber/22;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=483:2-3,7-8,12-13;flags=;id=3695cb46-f70a-4d6f-a71b-159d434c45b5;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1594557379272;turbo=0;user-id=40286300;user-type=mod :randers!randers@randers.tmi.twitch.tv PRIVMSG #pajlada :👉 <3 👉 <3 👉 <3"; let irc_message = IRCMessage::parse(src).unwrap(); let msg = PrivmsgMessage::try_from(irc_message).unwrap(); @@ -452,7 +457,7 @@ mod tests { #[test] fn test_emote_index_complete_out_of_range() { - // no overlap between string and specified range + // no overlap between FastStr and specified range let src = r"@badge-info=subscriber/3;badges=subscriber/3;color=#0000FF;display-name=Linkoping;emotes=25:44-48;flags=17-26:S.6;id=744f9c58-b180-4f46-bd9e-b515b5ef75c1;mod=0;room-id=188442366;subscriber=1;tmi-sent-ts=1566335866017;turbo=0;user-id=91673457;user-type= :linkoping!linkoping@linkoping.tmi.twitch.tv PRIVMSG #queenqarro :Då kan du begära skadestånd och förtal Kappa"; let irc_message = IRCMessage::parse(src).unwrap(); let msg = PrivmsgMessage::try_from(irc_message).unwrap(); @@ -469,7 +474,7 @@ mod tests { #[test] fn test_emote_index_beyond_out_of_range() { - // no overlap between string and specified range + // no overlap between FastStr and specified range let src = r"@badge-info=subscriber/3;badges=subscriber/3;color=#0000FF;display-name=Linkoping;emotes=25:45-49;flags=17-26:S.6;id=744f9c58-b180-4f46-bd9e-b515b5ef75c1;mod=0;room-id=188442366;subscriber=1;tmi-sent-ts=1566335866017;turbo=0;user-id=91673457;user-type= :linkoping!linkoping@linkoping.tmi.twitch.tv PRIVMSG #queenqarro :Då kan du begära skadestånd och förtal Kappa"; let irc_message = IRCMessage::parse(src).unwrap(); let msg = PrivmsgMessage::try_from(irc_message).unwrap(); diff --git a/src/message/commands/usernotice.rs b/src/message/commands/usernotice.rs index c711f95..6b20460 100644 --- a/src/message/commands/usernotice.rs +++ b/src/message/commands/usernotice.rs @@ -2,6 +2,7 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics}; use crate::message::{IRCMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; +use fast_str::FastStr; use std::convert::TryFrom; #[cfg(feature = "with-serde")] @@ -19,12 +20,18 @@ use {serde::Deserialize, serde::Serialize}; /// [`ReplyToMessage`](crate::message::ReplyToMessage) is not /// implemented for `UserNoticeMessage`. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct UserNoticeMessage { /// Login name of the channel that this message was sent to. - pub channel_login: String, + pub channel_login: FastStr, /// ID of the channel that this message was sent to. - pub channel_id: String, + pub channel_id: FastStr, /// The user that sent/triggered this message. Depending on the `event` (see below), /// this user may or may not have any actual meaning (for some type of events, this @@ -41,21 +48,21 @@ pub struct UserNoticeMessage { /// /// Currently the only event that can a message is a `resub`, where this message text is the /// message the user shared with the streamer alongside the resub message. - pub message_text: Option, + pub message_text: Option, /// A system message that is always present and represents a user-presentable message /// of what this event is, for example "FuchsGewand subscribed with Twitch Prime. /// They've subscribed for 12 months, currently on a 9 month streak!". /// /// This message is always present and always fully pre-formatted by Twitch /// with this event's parameters. - pub system_message: String, + pub system_message: FastStr, /// this holds the event-specific data, e.g. for sub, resub, subgift, etc... pub event: UserNoticeEvent, - /// String identifying the type of event (`msg-id` tag). Can be used to manually parse + /// FastStr identifying the type of event (`msg-id` tag). Can be used to manually parse /// undocumented types of `USERNOTICE` messages. - pub event_id: String, + pub event_id: FastStr, /// Metadata related to the chat badges in the `badges` tag. /// @@ -79,9 +86,9 @@ pub struct UserNoticeMessage { /// a pseudorandom but consistent-per-user color if they have no color specified. pub name_color: Option, - /// A string uniquely identifying this message. Can be used with the Twitch API to + /// A FastStr uniquely identifying this message. Can be used with the Twitch API to /// delete single messages. See also the `CLEARMSG` message type. - pub message_id: String, + pub message_id: FastStr, /// Timestamp of when this message was sent. pub server_timestamp: DateTime, @@ -94,12 +101,18 @@ pub struct UserNoticeMessage { /// if the upgrade happens as part of a seasonal promotion on Twitch, e.g. Subtember /// or similar. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct SubGiftPromo { /// Total number of subs gifted during this promotion pub total_gifts: u64, /// Friendly name of the promotion, e.g. `Subtember 2018` - pub promo_name: String, + pub promo_name: FastStr, } impl SubGiftPromo { @@ -112,6 +125,7 @@ impl SubGiftPromo { .try_get_optional_nonempty_tag_value("msg-param-promo-name")? .map(|s| s.to_owned()), ) { + let promo_name = FastStr::from_string(promo_name); Ok(Some(SubGiftPromo { total_gifts, promo_name, @@ -148,7 +162,13 @@ impl SubGiftPromo { /// added to it in the future, without the need for a breaking release. #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub enum UserNoticeEvent { /// Emitted when a user subscribes or resubscribes to a channel. /// The user sending this `USERNOTICE` is the user subscribing/resubscribing. @@ -164,10 +184,10 @@ pub enum UserNoticeEvent { /// Consecutive number of months the sending user has subscribed to this channel. streak_months: Option, /// `Prime`, `1000`, `2000` or `3000`, referring to Prime or tier 1, 2 or 3 subs respectively. - sub_plan: String, + sub_plan: FastStr, /// A name the broadcaster configured for this sub plan, e.g. `The Ninjas` or /// `Channel subscription (nymn_hs)` - sub_plan_name: String, + sub_plan_name: FastStr, }, /// Incoming raid to a channel. @@ -180,7 +200,7 @@ pub enum UserNoticeEvent { /// picture. /// /// E.g. `https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png` - profile_image_url: String, + profile_image_url: FastStr, }, /// Indicates a gifted subscription. @@ -199,10 +219,10 @@ pub enum UserNoticeEvent { /// The user that received this gifted subscription or resubscription. recipient: TwitchUserBasics, /// `1000`, `2000` or `3000`, referring to tier 1, 2 or 3 subs respectively. - sub_plan: String, + sub_plan: FastStr, /// A name the broadcaster configured for this sub plan, e.g. `The Ninjas` or /// `Channel subscription (nymn_hs)` - sub_plan_name: String, + sub_plan_name: FastStr, /// number of months in a single multi-month gift. num_gifted_months: u64, }, @@ -226,7 +246,7 @@ pub enum UserNoticeEvent { sender_total_gifts: u64, /// The type of sub plan the recipients were gifted. /// `1000`, `2000` or `3000`, referring to tier 1, 2 or 3 subs respectively. - sub_plan: String, + sub_plan: FastStr, }, /// This event precedes a wave of `subgift`/`anonsubgift` messages. @@ -243,7 +263,7 @@ pub enum UserNoticeEvent { mass_gift_count: u64, /// The type of sub plan the recipients were gifted. /// `1000`, `2000` or `3000`, referring to tier 1, 2 or 3 subs respectively. - sub_plan: String, + sub_plan: FastStr, }, /// Occurs when a user continues their gifted subscription they got from a non-anonymous @@ -255,11 +275,11 @@ pub enum UserNoticeEvent { /// User that originally gifted the sub to this user. /// This is the login name, see `TwitchUserBasics` for more info about the difference /// between id, login and name. - gifter_login: String, + gifter_login: FastStr, /// User that originally gifted the sub to this user. /// This is the (display) name name, see `TwitchUserBasics` for more info about the /// difference between id, login and name. - gifter_name: String, + gifter_name: FastStr, /// Present if this gift/upgrade is part of a Twitch gift sub promotion, e.g. /// Subtember or similar. promotion: Option, @@ -280,7 +300,7 @@ pub enum UserNoticeEvent { /// ` is new to 's chat! Say hello!` Ritual { /// currently only valid value: `new_chatter` - ritual_name: String, + ritual_name: FastStr, }, /// When a user cheers and earns himself a new bits badge with that cheer @@ -314,11 +334,9 @@ impl TryFrom for UserNoticeMessage { // @badge-info=subscriber/0;badges=subscriber/0,premium/1;color=#8A2BE2;display-name=PilotChup;emotes=;flags=;id=c7ae5c7a-3007-4f9d-9e64-35219a5c1134;login=pilotchup;mod=0;msg-id=sub;msg-param-cumulative-months=1;msg-param-months=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=Channel\sSubscription\s(xqcow);msg-param-sub-plan=Prime;room-id=71092938;subscriber=1;system-msg=PilotChup\ssubscribed\swith\sTwitch\sPrime.;tmi-sent-ts=1575162111790;user-id=40745007;user-type= :tmi.twitch.tv USERNOTICE #xqcow let sender = TwitchUserBasics { - id: source.try_get_nonempty_tag_value("user-id")?.to_owned(), - login: source.try_get_nonempty_tag_value("login")?.to_owned(), - name: source - .try_get_nonempty_tag_value("display-name")? - .to_owned(), + id: FastStr::from_ref(source.try_get_nonempty_tag_value("user-id")?), + login: FastStr::from_ref(source.try_get_nonempty_tag_value("login")?), + name: FastStr::from_ref(source.try_get_nonempty_tag_value("display-name")?), }; // the `msg-id` tag specifies the type of event this usernotice conveys. According to twitch, @@ -331,7 +349,7 @@ impl TryFrom for UserNoticeMessage { // (these can be added later) // each event then has additional tags beginning with `msg-param-`, see below - let event_id = source.try_get_nonempty_tag_value("msg-id")?.to_owned(); + let event_id = FastStr::from_ref(source.try_get_nonempty_tag_value("msg-id")?); let event = match event_id.as_str() { // sub, resub: // sender is the user subbing/resubbung @@ -348,12 +366,14 @@ impl TryFrom for UserNoticeMessage { } else { None }, - sub_plan: source - .try_get_nonempty_tag_value("msg-param-sub-plan")? - .to_owned(), - sub_plan_name: source - .try_get_nonempty_tag_value("msg-param-sub-plan-name")? - .to_owned(), + sub_plan: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan")?, + ), + sub_plan_name: FastStr::from( + source + .try_get_nonempty_tag_value("msg-param-sub-plan-name")? + .to_owned(), + ), }, // raid: // sender is the user raiding this channel @@ -363,9 +383,9 @@ impl TryFrom for UserNoticeMessage { // msg-param-profileImageURL (link to 70x70 version of raider's pfp) "raid" => UserNoticeEvent::Raid { viewer_count: source.try_get_number::("msg-param-viewerCount")?, - profile_image_url: source - .try_get_nonempty_tag_value("msg-param-profileImageURL")? - .to_owned(), + profile_image_url: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-profileImageURL")?, + ), }, // subgift, anonsubgift: // sender of message is the gifter, or AnAnonymousGifter (ID 274598607) @@ -381,22 +401,22 @@ impl TryFrom for UserNoticeMessage { is_sender_anonymous: event_id == "anonsubgift" || sender.id == "274598607", cumulative_months: source.try_get_number("msg-param-months")?, recipient: TwitchUserBasics { - id: source - .try_get_nonempty_tag_value("msg-param-recipient-id")? - .to_owned(), - login: source - .try_get_nonempty_tag_value("msg-param-recipient-user-name")? - .to_owned(), - name: source - .try_get_nonempty_tag_value("msg-param-recipient-display-name")? - .to_owned(), + id: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-recipient-id")?, + ), + login: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-recipient-user-name")?, + ), + name: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-recipient-display-name")?, + ), }, - sub_plan: source - .try_get_nonempty_tag_value("msg-param-sub-plan")? - .to_owned(), - sub_plan_name: source - .try_get_nonempty_tag_value("msg-param-sub-plan-name")? - .to_owned(), + sub_plan: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan")?, + ), + sub_plan_name: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan-name")?, + ), num_gifted_months: source.try_get_number("msg-param-gift-months")?, }, // submysterygift, anonsubmysterygift: @@ -416,18 +436,18 @@ impl TryFrom for UserNoticeMessage { { UserNoticeEvent::AnonSubMysteryGift { mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?, - sub_plan: source - .try_get_nonempty_tag_value("msg-param-sub-plan")? - .to_owned(), + sub_plan: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan")?, + ), } } // this takes over all other cases of submysterygift. "submysterygift" => UserNoticeEvent::SubMysteryGift { mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?, sender_total_gifts: source.try_get_number("msg-param-sender-count")?, - sub_plan: source - .try_get_nonempty_tag_value("msg-param-sub-plan")? - .to_owned(), + sub_plan: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan")?, + ), }, // giftpaidupgrade, anongiftpaidupgrade: // When a user commits to continue the gift sub by another user (or an anonymous gifter). @@ -442,12 +462,12 @@ impl TryFrom for UserNoticeMessage { // msg-param-sender-login - login name of user who gifted this user originally // msg-param-sender-name - display name of user who gifted this user originally "giftpaidupgrade" => UserNoticeEvent::GiftPaidUpgrade { - gifter_login: source - .try_get_nonempty_tag_value("msg-param-sender-login")? - .to_owned(), - gifter_name: source - .try_get_nonempty_tag_value("msg-param-sender-name")? - .to_owned(), + gifter_login: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sender-login")?, + ), + gifter_name: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sender-name")?, + ), promotion: SubGiftPromo::parse_if_present(&source)?, }, "anongiftpaidupgrade" => UserNoticeEvent::AnonGiftPaidUpgrade { @@ -460,9 +480,9 @@ impl TryFrom for UserNoticeMessage { // " is new to 's chat! Say hello!" // msg-param-ritual-name - only valid value: "new_chatter" "ritual" => UserNoticeEvent::Ritual { - ritual_name: source - .try_get_nonempty_tag_value("msg-param-ritual-name")? - .to_owned(), + ritual_name: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-ritual-name")?, + ), }, // bitsbadgetier @@ -471,9 +491,7 @@ impl TryFrom for UserNoticeMessage { // and just earned themselves the 10k bits badge) // msg-param-threshold - specifies the bits threshold, e.g. in the above example 10000 "bitsbadgetier" => UserNoticeEvent::BitsBadgeTier { - threshold: source - .try_get_number::("msg-param-threshold")? - .to_owned(), + threshold: source.try_get_number::("msg-param-threshold")?, }, // there are more events that are just not documented and not implemented yet. see above. @@ -488,19 +506,19 @@ impl TryFrom for UserNoticeMessage { }; Ok(UserNoticeMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + channel_id: FastStr::from_ref(source.try_get_nonempty_tag_value("room-id")?), sender, message_text, - system_message: source.try_get_nonempty_tag_value("system-msg")?.to_owned(), + system_message: FastStr::from_ref(source.try_get_nonempty_tag_value("system-msg")?), event, event_id, badge_info: source.try_get_badges("badge-info")?, badges: source.try_get_badges("badges")?, emotes, name_color: source.try_get_color("color")?, - message_id: source.try_get_nonempty_tag_value("id")?.to_owned(), - server_timestamp: source.try_get_timestamp("tmi-sent-ts")?.to_owned(), + message_id: FastStr::from_ref(source.try_get_nonempty_tag_value("id")?), + server_timestamp: source.try_get_timestamp("tmi-sent-ts")?, source, }) } @@ -719,7 +737,7 @@ mod tests { #[test] pub fn test_subgift_ananonymousgifter() { - let src = "@badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=62c3fd39-84cc-452a-9096-628a5306633a;login=ananonymousgifter;mod=0;msg-id=subgift;msg-param-fun-string=FunStringThree;msg-param-gift-months=1;msg-param-months=13;msg-param-origin-id=da\\s39\\sa3\\see\\s5e\\s6b\\s4b\\s0d\\s32\\s55\\sbf\\sef\\s95\\s60\\s18\\s90\\saf\\sd8\\s07\\s09;msg-param-recipient-display-name=Dot0422;msg-param-recipient-id=151784015;msg-param-recipient-user-name=dot0422;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\suser\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sDot0422!\\s;tmi-sent-ts=1594495108936;user-id=274598607;user-type= :tmi.twitch.tv USERNOTICE #xqcow"; + let src = "@badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=62c3fd39-84cc-452a-9096-628a5306633a;login=ananonymousgifter;mod=0;msg-id=subgift;msg-param-fun-FastStr=FunFastStrThree;msg-param-gift-months=1;msg-param-months=13;msg-param-origin-id=da\\s39\\sa3\\see\\s5e\\s6b\\s4b\\s0d\\s32\\s55\\sbf\\sef\\s95\\s60\\s18\\s90\\saf\\sd8\\s07\\s09;msg-param-recipient-display-name=Dot0422;msg-param-recipient-id=151784015;msg-param-recipient-user-name=dot0422;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\suser\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sDot0422!\\s;tmi-sent-ts=1594495108936;user-id=274598607;user-type= :tmi.twitch.tv USERNOTICE #xqcow"; let irc_message = IRCMessage::parse(src).unwrap(); let msg = UserNoticeMessage::try_from(irc_message).unwrap(); diff --git a/src/message/commands/userstate.rs b/src/message/commands/userstate.rs index c4dc9c8..634b3f6 100644 --- a/src/message/commands/userstate.rs +++ b/src/message/commands/userstate.rs @@ -1,8 +1,9 @@ +use fast_str::FastStr; + use crate::message::commands::IRCMessageParseExt; use crate::message::twitch::{Badge, RGBColor}; use crate::message::{IRCMessage, ServerMessageParseError}; use std::collections::HashSet; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; @@ -14,12 +15,18 @@ use {serde::Deserialize, serde::Serialize}; /// This message is similar to `GLOBALUSERSTATE`, but carries the context of a `channel_login` /// (and therefore possibly different `badges` and `badge_info`) and omits the `user_id`. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct UserStateMessage { /// Login name of the channel this `USERSTATE` message specifies the logged in user's state in. - pub channel_login: String, + pub channel_login: FastStr, /// (Display) name of the logged in user. - pub user_name: String, + pub user_name: FastStr, /// Metadata related to the chat badges in the `badges` tag. /// /// Currently this is used only for `subscriber`, to indicate the exact number of months @@ -30,7 +37,7 @@ pub struct UserStateMessage { /// List of badges the logged in user has in this channel. pub badges: Vec, /// List of emote set IDs the logged in user has available. This always contains at least 0. - pub emote_sets: HashSet, + pub emote_sets: HashSet, /// What name color the logged in user has chosen. The same color is used in all channels. pub name_color: Option, @@ -47,10 +54,8 @@ impl TryFrom for UserStateMessage { } Ok(UserStateMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - user_name: source - .try_get_nonempty_tag_value("display-name")? - .to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + user_name: FastStr::from_ref(source.try_get_nonempty_tag_value("display-name")?), badge_info: source.try_get_badges("badge-info")?, badges: source.try_get_badges("badges")?, emote_sets: source.try_get_emote_sets("emote-sets")?, diff --git a/src/message/commands/whisper.rs b/src/message/commands/whisper.rs index c7e59b2..ca22c94 100644 --- a/src/message/commands/whisper.rs +++ b/src/message/commands/whisper.rs @@ -1,20 +1,27 @@ +use fast_str::FastStr; + use crate::message::commands::IRCMessageParseExt; use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics}; use crate::message::{IRCMessage, ServerMessageParseError}; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// A incoming whisper message (a private user-to-user message). #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct WhisperMessage { /// The login name of the receiving user (the logged in user). - pub recipient_login: String, + pub recipient_login: FastStr, /// User details of the user that sent us this whisper (the sending user). pub sender: TwitchUserBasics, /// The text content of the message. - pub message_text: String, + pub message_text: FastStr, /// Name color of the sending user. pub name_color: Option, /// List of badges (that the sending user has) that should be displayed alongside the message. @@ -38,17 +45,15 @@ impl TryFrom for WhisperMessage { // example: // @badges=;color=#19E6E6;display-name=randers;emotes=25:22-26;message-id=1;thread-id=40286300_553170741;turbo=0;user-id=40286300;user-type= :randers!randers@randers.tmi.twitch.tv WHISPER randers811 :hello, this is a test Kappa - let message_text = source.try_get_param(1)?.to_owned(); + let message_text = FastStr::from_ref(source.try_get_param(1)?); let emotes = source.try_get_emotes("emotes", &message_text)?; Ok(WhisperMessage { - recipient_login: source.try_get_param(0)?.to_owned(), + recipient_login: FastStr::from_ref(source.try_get_param(0)?), sender: TwitchUserBasics { - id: source.try_get_nonempty_tag_value("user-id")?.to_owned(), - login: source.try_get_prefix_nickname()?.to_owned(), - name: source - .try_get_nonempty_tag_value("display-name")? - .to_owned(), + id: FastStr::from_ref(source.try_get_nonempty_tag_value("user-id")?), + login: FastStr::from_ref(source.try_get_prefix_nickname()?), + name: FastStr::from_ref(source.try_get_nonempty_tag_value("display-name")?), }, message_text, name_color: source.try_get_color("color")?, diff --git a/src/message/mod.rs b/src/message/mod.rs index f2e1f2f..21eb589 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -20,6 +20,7 @@ pub use commands::usernotice::{SubGiftPromo, UserNoticeEvent, UserNoticeMessage} pub use commands::userstate::UserStateMessage; pub use commands::whisper::WhisperMessage; pub use commands::{ServerMessage, ServerMessageParseError}; +use fast_str::FastStr; pub use prefix::IRCPrefix; pub use tags::IRCTags; pub use twitch::*; @@ -31,7 +32,7 @@ use thiserror::Error; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; -/// Error while parsing a string into an `IRCMessage`. +/// Error while parsing a FastStr into an `IRCMessage`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] pub enum IRCParseError { /// No space found after tags (no command/prefix) @@ -69,19 +70,19 @@ impl<'a, T: AsRawIRC> fmt::Display for RawIRCDisplay<'a, T> { pub trait AsRawIRC { /// Writes the raw IRC message to the given formatter. fn format_as_raw_irc(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; - /// Creates a new string with the raw IRC message. + /// Creates a new FastStr with the raw IRC message. /// - /// The resulting output string is guaranteed to parse to the same value it was created from, + /// The resulting output FastStr is guaranteed to parse to the same value it was created from, /// but due to protocol ambiguity it is not guaranteed to be identical to the input /// the value was parsed from (if it was parsed at all). /// /// For example, the order of tags might differ, or the use of trailing parameters /// might be different. - fn as_raw_irc(&self) -> String + fn as_raw_irc(&self) -> FastStr where Self: Sized, { - format!("{}", RawIRCDisplay(self)) + FastStr::from_string(format!("{}", RawIRCDisplay(self))) } } @@ -91,7 +92,13 @@ pub trait AsRawIRC { /// for the message format that this is based on. /// Further, this implements [IRCv3 tags](https://ircv3.net/specs/extensions/message-tags.html). #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct IRCMessage { /// A map of additional key-value tags on this message. pub tags: IRCTags, @@ -99,19 +106,19 @@ pub struct IRCMessage { /// server and/or user. pub prefix: Option, /// A command like `PRIVMSG` or `001` (see RFC 2812 for the definition). - pub command: String, + pub command: FastStr, /// A list of parameters on this IRC message. See RFC 2812 for the definition. /// /// Middle parameters and trailing parameters are treated the same here, and as long as /// there are no spaces in the last parameter, there is no way to tell if that parameter /// was a middle or trailing parameter when it was parsed. - pub params: Vec, + pub params: Vec, } /// Allows quick creation of simple IRC messages using a command and optional parameters. /// -/// The given command and parameters have to implement `From for String` if they are not -/// already of type `String`. +/// The given command and parameters have to implement `From for FastStr` if they are not +/// already of type `FastStr`. /// /// # Example /// @@ -139,11 +146,11 @@ macro_rules! irc { { let capacity = irc!(@count_exprs $($argument),*); #[allow(unused_mut)] - let mut temp_vec: ::std::vec::Vec = ::std::vec::Vec::with_capacity(capacity); + let mut temp_vec: Vec = ::std::vec::Vec::with_capacity(capacity); $( - temp_vec.push(::std::string::String::from($argument)); + temp_vec.push(fast_str::FastStr::from($argument)); )* - $crate::message::IRCMessage::new_simple(::std::string::String::from($command), temp_vec) + $crate::message::IRCMessage::new_simple(fast_str::FastStr::from($command), temp_vec) } }; } @@ -151,7 +158,7 @@ macro_rules! irc { impl IRCMessage { /// Create a new `IRCMessage` with just a command and parameters, similar to the /// `irc!` macro. - pub fn new_simple(command: String, params: Vec) -> IRCMessage { + pub fn new_simple(command: FastStr, params: Vec) -> IRCMessage { IRCMessage { tags: IRCTags::new(), prefix: None, @@ -164,8 +171,8 @@ impl IRCMessage { pub fn new( tags: IRCTags, prefix: Option, - command: String, - params: Vec, + command: FastStr, + params: Vec, ) -> IRCMessage { IRCMessage { tags, @@ -216,8 +223,11 @@ impl IRCMessage { let mut command_split = source.splitn(2, ' '); let mut command = command_split.next().unwrap().to_owned(); + command.make_ascii_uppercase(); + let command = FastStr::from_string(command); + if command.is_empty() || !command.chars().all(|c| c.is_ascii_alphabetic()) && !command.chars().all(|c| c.is_ascii() && c.is_numeric()) @@ -233,7 +243,7 @@ impl IRCMessage { while let Some(rest_str) = rest { if let Some(sub_str) = rest_str.strip_prefix(':') { // trailing param, remove : and consume the rest of the input - params.push(sub_str.to_owned()); + params.push(FastStr::from_ref(sub_str)); rest = None; } else { let mut split = rest_str.splitn(2, ' '); @@ -243,7 +253,7 @@ impl IRCMessage { if param.is_empty() { return Err(IRCParseError::TooManySpacesInMiddleParams); } - params.push(param.to_owned()); + params.push(FastStr::from_ref(param)); } } } else { @@ -809,7 +819,7 @@ mod tests { } #[test] - fn test_stringify_pass() { + fn test_FastStrify_pass() { assert_eq!( irc!["PASS", "oauth:9892879487293847"].as_raw_irc(), "PASS oauth:9892879487293847" diff --git a/src/message/twitch.rs b/src/message/twitch.rs index 5d34a29..80b579a 100644 --- a/src/message/twitch.rs +++ b/src/message/twitch.rs @@ -3,15 +3,23 @@ use std::fmt::{Display, Formatter}; use std::ops::Range; +use fast_str::FastStr; + #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// Set of information describing the basic details of a Twitch user. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct TwitchUserBasics { /// The user's unique ID, e.g. `103973901` - pub id: String, + pub id: FastStr, /// The user's login name. For many users, this is simply the lowercased version of their /// (display) name, but there are also many users where there is no direct relation between /// `login` and `name`. @@ -23,7 +31,7 @@ pub struct TwitchUserBasics { /// The `login` name is used in many places to refer to users, e.g. in the URL for their channel page, /// or also in almost all places on the Twitch IRC interface (e.g. when sending a message to a /// channel, you specify the channel by its login name instead of ID). - pub login: String, + pub login: FastStr, /// Display name of the user. When possible a user should be referred to using this name /// in user-facing contexts. /// @@ -31,7 +39,7 @@ pub struct TwitchUserBasics { /// should avoid making assumptions about the format of this value. /// For example, the `name` can contain non-ascii characters, it can contain spaces and /// it can have spaces at the start and end (albeit rare). - pub name: String, + pub name: FastStr, } /// An RGB color, used to color chat user's names. @@ -46,10 +54,16 @@ pub struct TwitchUserBasics { /// g: 0x00, /// b: 0x0F /// }; -/// assert_eq!(color.to_string(), "#12000F"); +/// assert_eq!(color.to_FastStr(), "#12000F"); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct RGBColor { /// Red component pub r: u8, @@ -67,12 +81,18 @@ impl Display for RGBColor { /// A single emote, appearing as part of a message. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct Emote { /// An ID identifying this emote. For example `25` for the "Kappa" emote, but can also be non-numeric, /// for example on emotes modified using Twitch channel points, e.g. /// `301512758_TK` for `pajaDent_TK` where `301512758` is the ID of the original `pajaDent` emote. - pub id: String, + pub id: FastStr, /// A range of characters in the original message where the emote is placed. /// /// As is documented on `Range`, the `start` index of this range is inclusive, while the @@ -81,45 +101,57 @@ pub struct Emote { /// This is always the exact range of characters that Twitch originally sent. /// Note that due to [a Twitch bug](https://github.com/twitchdev/issues/issues/104) /// (that this library intentionally works around), the character range specified here - /// might be out-of-bounds for the original message text string. + /// might be out-of-bounds for the original message text FastStr. pub char_range: Range, /// This is the text that this emote replaces, e.g. `Kappa` or `:)`. - pub code: String, + pub code: FastStr, } /// A single Twitch "badge" to be shown next to the user's name in chat. /// /// The combination of `name` and `version` fully describes the exact badge to display. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct Badge { - /// A string identifying the type of badge. For example, `admin`, `moderator` or `subscriber`. - pub name: String, + /// A FastStr identifying the type of badge. For example, `admin`, `moderator` or `subscriber`. + pub name: FastStr, /// A (usually) numeric version of this badge. Most badges only have one version (then usually /// version will be `0` or `1`), but other types of badges have different versions (e.g. `subscriber`) /// to differentiate between levels, or lengths, or similar, depending on the badge. - pub version: String, + pub version: FastStr, } /// If a message is sent in reply to another one, Twitch provides some basic information about the message /// that was replied to. It is optional, as not every message will be in reply to another message. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct ReplyParent { /// Message UUID that this message is replying to. - pub message_id: String, + pub message_id: FastStr, /// User that sent the message that is replying to. pub reply_parent_user: TwitchUserBasics, /// The text of the message that this message is replying to. - pub message_text: String, + pub message_text: FastStr, } /// Extract the `message_id` from a [`PrivmsgMessage`](crate::message::PrivmsgMessage) or directly -/// use an arbitrary [`String`] or [`&str`] as a message ID. This trait allows you to plug both +/// use an arbitrary [`FastStr`] or [`&str`] as a message ID. This trait allows you to plug both /// of these types directly into [`say_in_reply_to()`](crate::TwitchIRCClient::say_in_reply_to) /// for your convenience. /// -/// For tuples `(&str, &str)` or `(String, String)`, the first member is the login name +/// For tuples `(&str, &str)` or `(FastStr, FastStr)`, the first member is the login name /// of the channel the message was sent to, and the second member is the ID of the message /// to be deleted. /// @@ -130,7 +162,7 @@ pub struct ReplyParent { pub trait ReplyToMessage { /// Login name of the channel that the message was sent to. fn channel_login(&self) -> &str; - /// The unique string identifying the message, specified on the message via the `id` tag. + /// The unique FastStr identifying the message, specified on the message via the `id` tag. fn message_id(&self) -> &str; } @@ -165,7 +197,7 @@ mod tests { assert_eq!(d.message_id(), "def"); } - fn function_with_impl_arg(a: &impl ReplyToMessage) -> String { + fn function_with_impl_arg(a: &impl ReplyToMessage) -> FastStr { a.message_id().to_owned() } diff --git a/src/transport/tcp.rs b/src/transport/tcp.rs index 96fd251..bf17d67 100644 --- a/src/transport/tcp.rs +++ b/src/transport/tcp.rs @@ -200,8 +200,8 @@ impl Transport for TCPTransport { let message_sink = FramedWrite::new(write_half, BytesCodec::new()).with(move |msg: IRCMessage| { - let mut s = msg.as_raw_irc(); - s.push_str("\r\n"); + let s = msg.as_raw_irc(); + let s = format!("{}{}", s, "\r\n"); future::ready(Ok(Bytes::from(s))) }); From 83b913df2f6f1f88b8ac674521fea99b4853e3bb Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:57:00 +0300 Subject: [PATCH 2/8] put changes into log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65db309..35558b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Version numbers follow [Semantic Versioning](https://semver.org/). ## Unreleased +- All `String`s replaced with FastStr, thanks to `fast_str` crate author, @xxXyh1908. + - Breaking: Fixed a erroneous implementation of the IRCv3 tags: This crate now no longer differentiates between empty and missing IRCv3 tag values (e.g. `@key` is equivalent to `@key=`). The type of the `IRCTags` struct has changed to hold a `HashMap` instead of a `HashMap>`. From a5ce5b2adaa3595de1d325bce29144eaf5bfef3b Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:09:32 +0300 Subject: [PATCH 3/8] cleanup --- src/message/commands/clearmsg.rs | 1 - src/message/commands/join.rs | 1 - src/message/commands/mod.rs | 1 - src/message/commands/notice.rs | 9 +++++++-- src/message/commands/part.rs | 9 +++++++-- src/message/commands/ping.rs | 9 +++++++-- src/message/commands/pong.rs | 1 - src/message/commands/privmsg.rs | 1 - src/message/commands/reconnect.rs | 9 +++++++-- src/message/commands/roomstate.rs | 17 ++++++++++++++--- src/message/commands/usernotice.rs | 1 - src/message/mod.rs | 2 +- 12 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/message/commands/clearmsg.rs b/src/message/commands/clearmsg.rs index 07dca9a..6589cb1 100644 --- a/src/message/commands/clearmsg.rs +++ b/src/message/commands/clearmsg.rs @@ -2,7 +2,6 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; use fast_str::FastStr; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; diff --git a/src/message/commands/join.rs b/src/message/commands/join.rs index 3a77161..6149963 100644 --- a/src/message/commands/join.rs +++ b/src/message/commands/join.rs @@ -2,7 +2,6 @@ use fast_str::FastStr; use crate::message::commands::{IRCMessageParseExt, ServerMessageParseError}; use crate::message::IRCMessage; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; diff --git a/src/message/commands/mod.rs b/src/message/commands/mod.rs index 649c3fb..5216859 100644 --- a/src/message/commands/mod.rs +++ b/src/message/commands/mod.rs @@ -30,7 +30,6 @@ use crate::message::{ use chrono::{DateTime, TimeZone, Utc}; use fast_str::FastStr; use std::collections::HashSet; -use std::convert::TryFrom; use std::ops::Range; use std::str::FromStr; use thiserror::Error; diff --git a/src/message/commands/notice.rs b/src/message/commands/notice.rs index 13cb34c..d2f934b 100644 --- a/src/message/commands/notice.rs +++ b/src/message/commands/notice.rs @@ -1,13 +1,18 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// A user-facing notice sent by the server. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct NoticeMessage { /// The login name of the channel that this notice was sent to. There are cases where this /// is missing, for example when a `NOTICE` message is sent in response to a failed login diff --git a/src/message/commands/part.rs b/src/message/commands/part.rs index 4423d7d..bfacdef 100644 --- a/src/message/commands/part.rs +++ b/src/message/commands/part.rs @@ -1,13 +1,18 @@ use crate::message::commands::{IRCMessageParseExt, ServerMessageParseError}; use crate::message::IRCMessage; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// Message received when you successfully leave (part) a channel. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct PartMessage { /// Login name of the channel you parted. pub channel_login: String, diff --git a/src/message/commands/ping.rs b/src/message/commands/ping.rs index aac04ce..fd1d725 100644 --- a/src/message/commands/ping.rs +++ b/src/message/commands/ping.rs @@ -1,13 +1,18 @@ use crate::message::commands::ServerMessageParseError; use crate::message::IRCMessage; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// A `PING` connection-control message. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct PingMessage { /// The message that this `PingMessage` was parsed from. pub source: IRCMessage, diff --git a/src/message/commands/pong.rs b/src/message/commands/pong.rs index 1ad0688..a250a91 100644 --- a/src/message/commands/pong.rs +++ b/src/message/commands/pong.rs @@ -1,6 +1,5 @@ use crate::message::commands::ServerMessageParseError; use crate::message::IRCMessage; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; diff --git a/src/message/commands/privmsg.rs b/src/message/commands/privmsg.rs index 3b19636..c12551c 100644 --- a/src/message/commands/privmsg.rs +++ b/src/message/commands/privmsg.rs @@ -3,7 +3,6 @@ use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics}; use crate::message::{IRCMessage, ReplyParent, ReplyToMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; use fast_str::FastStr; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; diff --git a/src/message/commands/reconnect.rs b/src/message/commands/reconnect.rs index 6a4b2cd..4f4d7ae 100644 --- a/src/message/commands/reconnect.rs +++ b/src/message/commands/reconnect.rs @@ -1,14 +1,19 @@ use crate::message::commands::ServerMessageParseError; use crate::message::commands::ServerMessageParseError::MismatchedCommand; use crate::message::IRCMessage; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; /// Sent by the server to signal a connection to disconnect and reconnect #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct ReconnectMessage { /// The message that this `ReconnectMessage` was parsed from. pub source: IRCMessage, diff --git a/src/message/commands/roomstate.rs b/src/message/commands/roomstate.rs index 659fb17..199b58d 100644 --- a/src/message/commands/roomstate.rs +++ b/src/message/commands/roomstate.rs @@ -1,6 +1,5 @@ use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; -use std::convert::TryFrom; use std::time::Duration; #[cfg(feature = "with-serde")] @@ -14,7 +13,13 @@ use {serde::Deserialize, serde::Serialize}; /// a `ROOMSTATE` is sent only containing the new value for that particular setting. /// Other settings will be `None`. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub struct RoomStateMessage { /// Login name of the channel whose "room state" is updated. pub channel_login: String, @@ -65,7 +70,13 @@ pub struct RoomStateMessage { /// Specifies the followers-only mode a chat is in or was put in. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "with-serde", + derive( + Serialize, + Deserialize + ) +)] pub enum FollowersOnlyMode { /// Followers-only mode is/was disabled. All users, including user that are not followers, /// can send chat messages. diff --git a/src/message/commands/usernotice.rs b/src/message/commands/usernotice.rs index 6b20460..f26eb5b 100644 --- a/src/message/commands/usernotice.rs +++ b/src/message/commands/usernotice.rs @@ -3,7 +3,6 @@ use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics}; use crate::message::{IRCMessage, ServerMessageParseError}; use chrono::{DateTime, Utc}; use fast_str::FastStr; -use std::convert::TryFrom; #[cfg(feature = "with-serde")] use {serde::Deserialize, serde::Serialize}; diff --git a/src/message/mod.rs b/src/message/mod.rs index 21eb589..7ff6d10 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -819,7 +819,7 @@ mod tests { } #[test] - fn test_FastStrify_pass() { + fn test_stringify_pass() { assert_eq!( irc!["PASS", "oauth:9892879487293847"].as_raw_irc(), "PASS oauth:9892879487293847" From 172d4b356815f1bcc59b2d63ca25357b08048b2b Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:11:24 +0300 Subject: [PATCH 4/8] clippy fixes #1 --- src/message/commands/clearchat.rs | 2 +- src/message/commands/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/message/commands/clearchat.rs b/src/message/commands/clearchat.rs index 63f9954..24d6fb4 100644 --- a/src/message/commands/clearchat.rs +++ b/src/message/commands/clearchat.rs @@ -99,7 +99,7 @@ impl TryFrom for ClearChatMessage { ClearChatAction::UserTimedOut { user_login: user_login.clone(), // Clone allowed because it's params. FastStr may turn it into Arc. - user_id: user_id, + user_id, timeout_length: Duration::from_secs(ban_duration), } } diff --git a/src/message/commands/mod.rs b/src/message/commands/mod.rs index 5216859..41e66c6 100644 --- a/src/message/commands/mod.rs +++ b/src/message/commands/mod.rs @@ -295,7 +295,7 @@ impl IRCMessageParseExt for IRCMessage { if src.is_empty() { Ok(HashSet::new()) } else { - Ok(src.split(',').map(|s| FastStr::from_ref(s)).collect()) + Ok(src.split(',').map(FastStr::from_ref).collect()) } } From b88dca6bf4c218d1d9f5eb36d70b908e382eb7e1 Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:31:44 +0300 Subject: [PATCH 5/8] fix missed problems --- src/client/mod.rs | 3 +- src/connection/event_loop.rs | 1 - src/message/commands/clearchat.rs | 20 +-- src/message/commands/clearmsg.rs | 17 +- src/message/commands/globaluserstate.rs | 20 +-- src/message/commands/join.rs | 4 +- src/message/commands/notice.rs | 41 +++-- src/message/commands/part.rs | 14 +- src/message/commands/privmsg.rs | 146 ++++++++--------- src/message/commands/roomstate.rs | 26 ++-- src/message/commands/usernotice.rs | 179 +++++++++++---------- src/message/commands/userstate.rs | 18 +-- src/message/commands/whisper.rs | 14 +- src/message/mod.rs | 198 +++++++++++------------- src/message/twitch.rs | 8 +- 15 files changed, 351 insertions(+), 358 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index bc42565..2a836c2 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -303,7 +303,8 @@ impl TwitchIRCClient { /// /// Returns a [validate::Error] if the passed `channel_login` is of /// [invalid format](crate::validate::validate_login). Returns `Ok(())` otherwise. - pub fn join(&self, channel_login: FastStr) -> Result<(), validate::Error> { + pub fn join(&self, channel_login: impl Into) -> Result<(), validate::Error> { + let channel_login = channel_login.into(); validate_login(&channel_login)?; self.client_loop_tx diff --git a/src/connection/event_loop.rs b/src/connection/event_loop.rs index 33de5a7..3d0ae69 100644 --- a/src/connection/event_loop.rs +++ b/src/connection/event_loop.rs @@ -13,7 +13,6 @@ use either::Either; use enum_dispatch::enum_dispatch; use futures_util::{SinkExt, StreamExt}; use std::collections::VecDeque; -use std::convert::TryFrom; use std::sync::{Arc, Weak}; use tokio::sync::{mpsc, oneshot}; use tokio::time::{interval_at, Duration, Instant}; diff --git a/src/message/commands/clearchat.rs b/src/message/commands/clearchat.rs index 24d6fb4..c7c9f43 100644 --- a/src/message/commands/clearchat.rs +++ b/src/message/commands/clearchat.rs @@ -145,11 +145,11 @@ mod tests { assert_eq!( msg, ClearChatMessage { - channel_login: "pajlada".to_owned(), - channel_id: "11148817".to_owned(), + channel_login: "pajlada".into(), + channel_id: "11148817".into(), action: ClearChatAction::UserTimedOut { - user_login: "fabzeef".to_owned(), - user_id: "148973258".to_owned(), + user_login: "fabzeef".into(), + user_id: "148973258".into(), timeout_length: Duration::from_secs(1) }, server_timestamp: Utc.timestamp_millis_opt(1594553828245).unwrap(), @@ -167,11 +167,11 @@ mod tests { assert_eq!( msg, ClearChatMessage { - channel_login: "pajlada".to_owned(), - channel_id: "11148817".to_owned(), + channel_login: "pajlada".into(), + channel_id: "11148817".into(), action: ClearChatAction::UserBanned { - user_login: "weeb123".to_owned(), - user_id: "70948394".to_owned(), + user_login: "weeb123".into(), + user_id: "70948394".into(), }, server_timestamp: Utc.timestamp_millis_opt(1594561360331).unwrap(), source: irc_message @@ -188,8 +188,8 @@ mod tests { assert_eq!( msg, ClearChatMessage { - channel_login: "randers".to_owned(), - channel_id: "40286300".to_owned(), + channel_login: "randers".into(), + channel_id: "40286300".into(), action: ClearChatAction::ChatCleared, server_timestamp: Utc.timestamp_millis_opt(1594561392337).unwrap(), source: irc_message diff --git a/src/message/commands/clearmsg.rs b/src/message/commands/clearmsg.rs index 6589cb1..23c1261 100644 --- a/src/message/commands/clearmsg.rs +++ b/src/message/commands/clearmsg.rs @@ -53,7 +53,6 @@ impl TryFrom for ClearMsgMessage { Ok(ClearMsgMessage { channel_login: FastStr::from_ref(source.try_get_channel_login()?), - // channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), sender_login: FastStr::from_ref(source.try_get_nonempty_tag_value("login")?), message_id: FastStr::from_ref(source.try_get_nonempty_tag_value("target-msg-id")?), server_timestamp: source.try_get_timestamp("tmi-sent-ts")?, @@ -85,10 +84,10 @@ mod tests { assert_eq!( msg, ClearMsgMessage { - channel_login: "pajlada".to_owned(), - sender_login: "alazymeme".to_owned(), - message_id: "3c92014f-340a-4dc3-a9c9-e5cf182f4a84".to_owned(), - message_text: "NIGHT CUNT".to_owned(), + channel_login: "pajlada".into(), + sender_login: "alazymeme".into(), + message_id: "3c92014f-340a-4dc3-a9c9-e5cf182f4a84".into(), + message_text: "NIGHT CUNT".into(), is_action: false, server_timestamp: Utc.timestamp_millis_opt(1594561955611).unwrap(), source: irc_message @@ -105,10 +104,10 @@ mod tests { assert_eq!( msg, ClearMsgMessage { - channel_login: "pajlada".to_owned(), - sender_login: "randers".to_owned(), - message_id: "15e5164d-f8e6-4aec-baf4-2d6a330760c4".to_owned(), - message_text: "test".to_owned(), + channel_login: "pajlada".into(), + sender_login: "randers".into(), + message_id: "15e5164d-f8e6-4aec-baf4-2d6a330760c4".into(), + message_text: "test".into(), is_action: true, server_timestamp: Utc.timestamp_millis_opt(1594562632383).unwrap(), source: irc_message diff --git a/src/message/commands/globaluserstate.rs b/src/message/commands/globaluserstate.rs index 72e2c0b..4400135 100644 --- a/src/message/commands/globaluserstate.rs +++ b/src/message/commands/globaluserstate.rs @@ -91,13 +91,13 @@ mod tests { assert_eq!( msg, GlobalUserStateMessage { - user_id: "40286300".to_owned(), - user_name: "randers".to_owned(), + user_id: "40286300".into(), + user_name: "randers".into(), badge_info: vec![], badges: vec![], emote_sets: vec!["0", "42", "237"] .into_iter() - .map(|s| s.to_owned()) + .map(|s| s.into()) .collect(), name_color: Some(RGBColor { r: 0x19, @@ -120,12 +120,12 @@ mod tests { assert_eq!( msg, GlobalUserStateMessage { - user_id: "40286300".to_owned(), - user_name: "randers".to_owned(), + user_id: "40286300".into(), + user_name: "randers".into(), badge_info: vec![], badges: vec![Badge { - name: "premium".to_owned(), - version: "1".to_owned() + name: "premium".into(), + version: "1".into() }], emote_sets: HashSet::new(), name_color: None, @@ -144,11 +144,11 @@ mod tests { assert_eq!( msg, GlobalUserStateMessage { - user_id: "553170741".to_owned(), - user_name: "randers811".to_owned(), + user_id: "553170741".into(), + user_name: "randers811".into(), badge_info: vec![], badges: vec![], - emote_sets: HashSet::from_iter(vec!["0".to_owned()]), + emote_sets: HashSet::from_iter(vec!["0".into()]), name_color: None, source: irc_message } diff --git a/src/message/commands/join.rs b/src/message/commands/join.rs index 6149963..87ee41f 100644 --- a/src/message/commands/join.rs +++ b/src/message/commands/join.rs @@ -62,8 +62,8 @@ mod tests { assert_eq!( msg, JoinMessage { - channel_login: "pajlada".to_owned(), - user_login: "randers811".to_owned(), + channel_login: "pajlada".into(), + user_login: "randers811".into(), source: irc_message } ) diff --git a/src/message/commands/notice.rs b/src/message/commands/notice.rs index d2f934b..af947dd 100644 --- a/src/message/commands/notice.rs +++ b/src/message/commands/notice.rs @@ -1,3 +1,5 @@ +use fast_str::FastStr; + use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; @@ -17,13 +19,13 @@ pub struct NoticeMessage { /// The login name of the channel that this notice was sent to. There are cases where this /// is missing, for example when a `NOTICE` message is sent in response to a failed login /// attempt. - pub channel_login: Option, - /// Message content of the notice. This is some user-friendly string, e.g. + pub channel_login: Option, + /// Message content of the notice. This is some user-friendly FastStr, e.g. /// `You are permanently banned from talking in .` - pub message_text: String, - /// If present, a computer-readable string identifying the class/type of notice. + pub message_text: FastStr, + /// If present, a computer-readable FastStr identifying the class/type of notice. /// For example `msg_banned`. These message IDs are [documented by Twitch here](https://dev.twitch.tv/docs/irc/msg-id). - pub message_id: Option, + pub message_id: Option, /// The message that this `NoticeMessage` was parsed from. pub source: IRCMessage, @@ -36,15 +38,20 @@ impl TryFrom for NoticeMessage { if source.command != "NOTICE" { return Err(ServerMessageParseError::MismatchedCommand(source)); } - Ok(NoticeMessage { - channel_login: source - .try_get_optional_channel_login()? - .map(|s| s.to_owned()), - message_text: source.try_get_param(1)?.to_owned(), - message_id: source - .try_get_optional_nonempty_tag_value("msg-id")? - .map(|s| s.to_owned()), + channel_login: { + match source.try_get_optional_channel_login()? { + Some(channel_login) => Some(FastStr::from_ref(channel_login)), + None => None, + } + }, + message_text: FastStr::from_ref(source.try_get_param(1)?), + message_id: { + match source.try_get_optional_nonempty_tag_value("msg-id")? { + Some(message_id) => Some(FastStr::from_ref(message_id)), + None => todo!(), + } + }, source, }) } @@ -70,9 +77,9 @@ mod tests { assert_eq!( msg, NoticeMessage { - channel_login: Some("forsen".to_owned()), - message_text: "You are permanently banned from talking in forsen.".to_owned(), - message_id: Some("msg_banned".to_owned()), + channel_login: Some("forsen".into()), + message_text: "You are permanently banned from talking in forsen.".into(), + message_id: Some("msg_banned".into()), source: irc_message } ) @@ -89,7 +96,7 @@ mod tests { msg, NoticeMessage { channel_login: None, - message_text: "Improperly formatted auth".to_owned(), + message_text: "Improperly formatted auth".into(), message_id: None, source: irc_message } diff --git a/src/message/commands/part.rs b/src/message/commands/part.rs index bfacdef..aa359c0 100644 --- a/src/message/commands/part.rs +++ b/src/message/commands/part.rs @@ -1,3 +1,5 @@ +use fast_str::FastStr; + use crate::message::commands::{IRCMessageParseExt, ServerMessageParseError}; use crate::message::IRCMessage; @@ -15,10 +17,10 @@ use {serde::Deserialize, serde::Serialize}; )] pub struct PartMessage { /// Login name of the channel you parted. - pub channel_login: String, + pub channel_login: FastStr, /// The login name of the logged in user (the login name of the user that parted the channel, /// which is the logged in user). - pub user_login: String, + pub user_login: FastStr, /// The message that this `PartMessage` was parsed from. pub source: IRCMessage, } @@ -32,8 +34,8 @@ impl TryFrom for PartMessage { } Ok(PartMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - user_login: source.try_get_prefix_nickname()?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + user_login: FastStr::from_ref(source.try_get_prefix_nickname()?), source, }) } @@ -59,8 +61,8 @@ mod tests { assert_eq!( msg, PartMessage { - channel_login: "pajlada".to_owned(), - user_login: "randers811".to_owned(), + channel_login: "pajlada".into(), + user_login: "randers811".into(), source: irc_message } ) diff --git a/src/message/commands/privmsg.rs b/src/message/commands/privmsg.rs index c12551c..bebd5ee 100644 --- a/src/message/commands/privmsg.rs +++ b/src/message/commands/privmsg.rs @@ -131,14 +131,14 @@ mod tests { assert_eq!( msg, PrivmsgMessage { - channel_login: "pajlada".to_owned(), - channel_id: "11148817".to_owned(), - message_text: "dank cam".to_owned(), + channel_login: "pajlada".into(), + channel_id: "11148817".into(), + message_text: "dank cam".into(), is_action: false, sender: TwitchUserBasics { - id: "29803735".to_owned(), - login: "jun1orrrr".to_owned(), - name: "JuN1oRRRR".to_owned() + id: "29803735".into(), + login: "jun1orrrr".into(), + name: "JuN1oRRRR".into() }, badge_info: vec![], badges: vec![], @@ -150,7 +150,7 @@ mod tests { }), emotes: vec![], server_timestamp: Utc.timestamp_millis_opt(1594545155039).unwrap(), - message_id: "e9d998c3-36f1-430f-89ec-6b887c28af36".to_owned(), + message_id: "e9d998c3-36f1-430f-89ec-6b887c28af36".into(), reply_parent: None, source: irc_message @@ -167,27 +167,27 @@ mod tests { assert_eq!( msg, PrivmsgMessage { - channel_login: "pajlada".to_owned(), - channel_id: "11148817".to_owned(), - message_text: "-tags".to_owned(), + channel_login: "pajlada".into(), + channel_id: "11148817".into(), + message_text: "-tags".into(), is_action: true, sender: TwitchUserBasics { - id: "40286300".to_owned(), - login: "randers".to_owned(), - name: "randers".to_owned() + id: "40286300".into(), + login: "randers".into(), + name: "randers".into() }, badge_info: vec![Badge { - name: "subscriber".to_owned(), - version: "22".to_owned() + name: "subscriber".into(), + version: "22".into() }], badges: vec![ Badge { - name: "moderator".to_owned(), - version: "1".to_owned() + name: "moderator".into(), + version: "1".into() }, Badge { - name: "subscriber".to_owned(), - version: "12".to_owned() + name: "subscriber".into(), + version: "12".into() } ], bits: None, @@ -198,7 +198,7 @@ mod tests { }), emotes: vec![], server_timestamp: Utc.timestamp_millis_opt(1594555275886).unwrap(), - message_id: "d831d848-b7c7-4559-ae3a-2cb88f4dbfed".to_owned(), + message_id: "d831d848-b7c7-4559-ae3a-2cb88f4dbfed".into(), reply_parent: None, source: irc_message } @@ -214,14 +214,14 @@ mod tests { assert_eq!( msg, PrivmsgMessage { - channel_login: "forsen".to_owned(), - channel_id: "22484632".to_owned(), - message_text: "NaM".to_owned(), + channel_login: "forsen".into(), + channel_id: "22484632".into(), + message_text: "NaM".into(), is_action: false, sender: TwitchUserBasics { - id: "467684514".to_owned(), - login: "carvedtaleare".to_owned(), - name: "CarvedTaleare".to_owned() + id: "467684514".into(), + login: "carvedtaleare".into(), + name: "CarvedTaleare".into() }, badge_info: vec![], badges: vec![], @@ -229,7 +229,7 @@ mod tests { name_color: None, emotes: vec![], server_timestamp: Utc.timestamp_millis_opt(1594554085753).unwrap(), - message_id: "c9b941d9-a0ab-4534-9903-971768fcdf10".to_owned(), + message_id: "c9b941d9-a0ab-4534-9903-971768fcdf10".into(), reply_parent: None, source: irc_message @@ -246,14 +246,14 @@ mod tests { assert_eq!( msg, PrivmsgMessage { - channel_login: "retoon".to_owned(), - channel_id: "37940952".to_owned(), - message_text: "@Retoon yes".to_owned(), + channel_login: "retoon".into(), + channel_id: "37940952".into(), + message_text: "@Retoon yes".into(), is_action: false, sender: TwitchUserBasics { - id: "133651738".to_owned(), - login: "leftswing".to_owned(), - name: "LeftSwing".to_owned() + id: "133651738".into(), + login: "leftswing".into(), + name: "LeftSwing".into() }, badge_info: vec![], badges: vec![], @@ -261,15 +261,15 @@ mod tests { name_color: None, emotes: vec![], server_timestamp: Utc.timestamp_millis_opt(1673925983585).unwrap(), - message_id: "5b4f63a9-776f-4fce-bf3c-d9707f52e32d".to_owned(), + message_id: "5b4f63a9-776f-4fce-bf3c-d9707f52e32d".into(), reply_parent: Some(ReplyParent { - message_id: "6b13e51b-7ecb-43b5-ba5b-2bb5288df696".to_owned(), + message_id: "6b13e51b-7ecb-43b5-ba5b-2bb5288df696".into(), reply_parent_user: TwitchUserBasics { - id: "37940952".to_owned(), - login: "retoon".to_FastStr(), - name: "Retoon".to_owned(), + id: "37940952".into(), + login: "retoon".into(), + name: "Retoon".into(), }, - message_text: "hello".to_owned() + message_text: "hello".into() }), source: irc_message @@ -311,49 +311,49 @@ mod tests { msg.emotes, vec![ Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 0, end: 5 }, - code: "Kappa".to_owned() + code: "Kappa".into() }, Emote { - id: "1902".to_owned(), + id: "1902".into(), char_range: Range { start: 6, end: 11 }, - code: "Keepo".to_owned() + code: "Keepo".into() }, Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 12, end: 17 }, - code: "Kappa".to_owned() + code: "Kappa".into() }, Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 18, end: 23 }, - code: "Kappa".to_owned() + code: "Kappa".into() }, Emote { - id: "1902".to_owned(), + id: "1902".into(), char_range: Range { start: 29, end: 34 }, - code: "Keepo".to_owned() + code: "Keepo".into() }, Emote { - id: "1902".to_owned(), + id: "1902".into(), char_range: Range { start: 35, end: 40 }, - code: "Keepo".to_owned() + code: "Keepo".into() }, Emote { - id: "499".to_owned(), + id: "499".into(), char_range: Range { start: 45, end: 47 }, - code: ":)".to_owned() + code: ":)".into() }, Emote { - id: "499".to_owned(), + id: "499".into(), char_range: Range { start: 48, end: 50 }, - code: ":)".to_owned() + code: ":)".into() }, Emote { - id: "490".to_owned(), + id: "490".into(), char_range: Range { start: 51, end: 53 }, - code: ":P".to_owned() + code: ":P".into() }, ] ); @@ -368,9 +368,9 @@ mod tests { assert_eq!( msg.emotes, vec![Emote { - id: "300196486_TK".to_owned(), + id: "300196486_TK".into(), char_range: Range { start: 0, end: 8 }, - code: "pajaM_TK".to_owned() + code: "pajaM_TK".into() },] ); } @@ -386,19 +386,19 @@ mod tests { msg.emotes, vec![ Emote { - id: "483".to_owned(), + id: "483".into(), char_range: Range { start: 2, end: 4 }, - code: "<3".to_owned() + code: "<3".into() }, Emote { - id: "483".to_owned(), + id: "483".into(), char_range: Range { start: 7, end: 9 }, - code: "<3".to_owned() + code: "<3".into() }, Emote { - id: "483".to_owned(), + id: "483".into(), char_range: Range { start: 12, end: 14 }, - code: "<3".to_owned() + code: "<3".into() }, ] ); @@ -422,9 +422,9 @@ mod tests { assert_eq!( msg.emotes, vec![Emote { - id: "425618".to_owned(), + id: "425618".into(), char_range: 49..52, - code: "UL".to_owned(), + code: "UL".into(), }] ); assert_eq!( @@ -443,9 +443,9 @@ mod tests { assert_eq!( msg.emotes, vec![Emote { - id: "25".to_owned(), + id: "25".into(), char_range: 41..46, - code: "ppa".to_owned(), + code: "ppa".into(), }] ); assert_eq!( @@ -464,9 +464,9 @@ mod tests { assert_eq!( msg.emotes, vec![Emote { - id: "25".to_owned(), + id: "25".into(), char_range: 44..49, - code: "".to_owned(), + code: "".into(), }] ); } @@ -481,9 +481,9 @@ mod tests { assert_eq!( msg.emotes, vec![Emote { - id: "25".to_owned(), + id: "25".into(), char_range: 45..50, - code: "".to_owned(), + code: "".into(), }] ); } diff --git a/src/message/commands/roomstate.rs b/src/message/commands/roomstate.rs index 199b58d..cab5b48 100644 --- a/src/message/commands/roomstate.rs +++ b/src/message/commands/roomstate.rs @@ -1,3 +1,5 @@ +use fast_str::FastStr; + use crate::message::commands::IRCMessageParseExt; use crate::message::{IRCMessage, ServerMessageParseError}; use std::time::Duration; @@ -22,9 +24,9 @@ use {serde::Deserialize, serde::Serialize}; )] pub struct RoomStateMessage { /// Login name of the channel whose "room state" is updated. - pub channel_login: String, + pub channel_login: FastStr, /// ID of the channel whose "room state" is updated. - pub channel_id: String, + pub channel_id: FastStr, /// If present, specifies a new setting for the "emote only" mode. /// (Controlled by `/emoteonly` and `/emoteonlyoff` commands in chat) @@ -114,8 +116,8 @@ impl TryFrom for RoomStateMessage { // is slow=0, anything other than that is enabled Ok(RoomStateMessage { - channel_login: source.try_get_channel_login()?.to_owned(), - channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(), + channel_login: FastStr::from_ref(source.try_get_channel_login()?), + channel_id: FastStr::from_ref(source.try_get_nonempty_tag_value("room-id")?), emote_only: source.try_get_optional_bool("emote-only")?, followers_only: source .try_get_optional_number::("followers-only")? @@ -155,8 +157,8 @@ mod tests { assert_eq!( msg, RoomStateMessage { - channel_login: "randers".to_owned(), - channel_id: "40286300".to_owned(), + channel_login: "randers".into(), + channel_id: "40286300".into(), emote_only: Some(false), followers_only: Some(FollowersOnlyMode::Disabled), r9k: Some(false), @@ -176,8 +178,8 @@ mod tests { assert_eq!( msg, RoomStateMessage { - channel_login: "randers".to_owned(), - channel_id: "40286300".to_owned(), + channel_login: "randers".into(), + channel_id: "40286300".into(), emote_only: Some(true), followers_only: Some(FollowersOnlyMode::Enabled(Duration::from_secs(0))), r9k: Some(true), @@ -209,8 +211,8 @@ mod tests { assert_eq!( msg, RoomStateMessage { - channel_login: "randers".to_owned(), - channel_id: "40286300".to_owned(), + channel_login: "randers".into(), + channel_id: "40286300".into(), emote_only: None, followers_only: None, r9k: None, @@ -230,8 +232,8 @@ mod tests { assert_eq!( msg, RoomStateMessage { - channel_login: "randers".to_owned(), - channel_id: "40286300".to_owned(), + channel_login: "randers".into(), + channel_id: "40286300".into(), emote_only: Some(true), followers_only: None, r9k: None, diff --git a/src/message/commands/usernotice.rs b/src/message/commands/usernotice.rs index f26eb5b..d973bd8 100644 --- a/src/message/commands/usernotice.rs +++ b/src/message/commands/usernotice.rs @@ -120,11 +120,9 @@ impl SubGiftPromo { ) -> Result, ServerMessageParseError> { if let (Some(total_gifts), Some(promo_name)) = ( source.try_get_optional_number("msg-param-promo-gift-total")?, - source - .try_get_optional_nonempty_tag_value("msg-param-promo-name")? - .map(|s| s.to_owned()), + source.try_get_optional_nonempty_tag_value("msg-param-promo-name")?, ) { - let promo_name = FastStr::from_string(promo_name); + let promo_name = FastStr::from_ref(promo_name); Ok(Some(SubGiftPromo { total_gifts, promo_name, @@ -368,10 +366,8 @@ impl TryFrom for UserNoticeMessage { sub_plan: FastStr::from_ref( source.try_get_nonempty_tag_value("msg-param-sub-plan")?, ), - sub_plan_name: FastStr::from( - source - .try_get_nonempty_tag_value("msg-param-sub-plan-name")? - .to_owned(), + sub_plan_name: FastStr::from_ref( + source.try_get_nonempty_tag_value("msg-param-sub-plan-name")?, ), }, // raid: @@ -546,40 +542,40 @@ mod tests { assert_eq!( msg, UserNoticeMessage { - channel_login: "xqcow".to_owned(), - channel_id: "71092938".to_owned(), + channel_login: "xqcow".into(), + channel_id: "71092938".into(), sender: TwitchUserBasics { - id: "224005980".to_owned(), - login: "fallenseraphhh".to_owned(), - name: "fallenseraphhh".to_owned(), + id: "224005980".into(), + login: "fallenseraphhh".into(), + name: "fallenseraphhh".into(), }, message_text: None, - system_message: "fallenseraphhh subscribed with Twitch Prime.".to_owned(), + system_message: "fallenseraphhh subscribed with Twitch Prime.".into(), event: UserNoticeEvent::SubOrResub { is_resub: false, cumulative_months: 1, streak_months: None, - sub_plan: "Prime".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "Prime".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), }, - event_id: "sub".to_owned(), + event_id: "sub".into(), badge_info: vec![Badge { - name: "subscriber".to_owned(), - version: "0".to_owned(), + name: "subscriber".into(), + version: "0".into(), }], badges: vec![ Badge { - name: "subscriber".to_owned(), - version: "0".to_owned(), + name: "subscriber".into(), + version: "0".into(), }, Badge { - name: "premium".to_owned(), - version: "1".to_owned(), + name: "premium".into(), + version: "1".into(), } ], emotes: vec![], name_color: None, - message_id: "2a9bea11-a80a-49a0-a498-1642d457f775".to_owned(), + message_id: "2a9bea11-a80a-49a0-a498-1642d457f775".into(), server_timestamp: Utc.timestamp_millis_opt(1582685713242).unwrap(), source: irc_message, } @@ -595,42 +591,42 @@ mod tests { assert_eq!( msg, UserNoticeMessage { - channel_login: "xqcow".to_owned(), - channel_id: "71092938".to_owned(), + channel_login: "xqcow".into(), + channel_id: "71092938".into(), sender: TwitchUserBasics { - id: "21156217".to_owned(), - login: "gutrin".to_owned(), - name: "Gutrin".to_owned(), + id: "21156217".into(), + login: "gutrin".into(), + name: "Gutrin".into(), }, - message_text: Some("xqcL".to_owned()), - system_message: "Gutrin subscribed at Tier 1. They've subscribed for 2 months, currently on a 2 month streak!".to_owned(), + message_text: Some("xqcL".into()), + system_message: "Gutrin subscribed at Tier 1. They've subscribed for 2 months, currently on a 2 month streak!".into(), event: UserNoticeEvent::SubOrResub { is_resub: true, cumulative_months: 2, streak_months: Some(2), - sub_plan: "1000".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "1000".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), }, - event_id: "resub".to_owned(), + event_id: "resub".into(), badge_info: vec![Badge { - name: "subscriber".to_owned(), - version: "2".to_owned(), + name: "subscriber".into(), + version: "2".into(), }], badges: vec![ Badge { - name: "subscriber".to_owned(), - version: "0".to_owned(), + name: "subscriber".into(), + version: "0".into(), }, Badge { - name: "battlerite_1".to_owned(), - version: "1".to_owned(), + name: "battlerite_1".into(), + version: "1".into(), } ], emotes: vec![ Emote { - id: "1035663".to_owned(), + id: "1035663".into(), char_range: Range { start: 0, end: 4 }, - code: "xqcL".to_owned(), + code: "xqcL".into(), } ], name_color: Some(RGBColor { @@ -638,7 +634,7 @@ mod tests { g: 0x00, b: 0xFF, }), - message_id: "e0975c76-054c-4954-8cb0-91b8867ec1ca".to_owned(), + message_id: "e0975c76-054c-4954-8cb0-91b8867ec1ca".into(), server_timestamp: Utc.timestamp_millis_opt(1581713640019).unwrap(), source: irc_message, } @@ -654,29 +650,28 @@ mod tests { assert_eq!( msg, UserNoticeMessage { - channel_login: "xqcow".to_owned(), - channel_id: "71092938".to_owned(), + channel_login: "xqcow".into(), + channel_id: "71092938".into(), sender: TwitchUserBasics { - id: "171356987".to_owned(), - login: "rene_rs".to_owned(), - name: "rene_rs".to_owned(), + id: "171356987".into(), + login: "rene_rs".into(), + name: "rene_rs".into(), }, message_text: None, system_message: - "rene_rs subscribed with Twitch Prime. They've subscribed for 11 months!" - .to_owned(), + "rene_rs subscribed with Twitch Prime. They've subscribed for 11 months!".into(), event: UserNoticeEvent::SubOrResub { is_resub: true, cumulative_months: 11, streak_months: None, - sub_plan: "Prime".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "Prime".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), }, - event_id: "resub".to_owned(), + event_id: "resub".into(), badge_info: vec![], badges: vec![Badge { - name: "premium".to_owned(), - version: "1".to_owned(), + name: "premium".into(), + version: "1".into(), },], emotes: vec![], name_color: Some(RGBColor { @@ -684,7 +679,7 @@ mod tests { g: 0x2B, b: 0xE2, }), - message_id: "ca1f02fb-77ec-487d-a9b3-bc4bfef2fe8b".to_owned(), + message_id: "ca1f02fb-77ec-487d-a9b3-bc4bfef2fe8b".into(), server_timestamp: Utc.timestamp_millis_opt(1590628650446).unwrap(), source: irc_message, } @@ -700,14 +695,14 @@ mod tests { assert_eq!( msg.sender, TwitchUserBasics { - id: "155874595".to_owned(), - login: "iamelisabete".to_owned(), - name: "iamelisabete".to_owned(), + id: "155874595".into(), + login: "iamelisabete".into(), + name: "iamelisabete".into(), } ); assert_eq!(msg.event, UserNoticeEvent::Raid { viewer_count: 430, - profile_image_url: "https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png".to_owned(), + profile_image_url: "https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png".into(), }); } @@ -723,12 +718,12 @@ mod tests { is_sender_anonymous: false, cumulative_months: 2, recipient: TwitchUserBasics { - id: "236653628".to_owned(), - login: "qatarking24xd".to_owned(), - name: "qatarking24xd".to_owned(), + id: "236653628".into(), + login: "qatarking24xd".into(), + name: "qatarking24xd".into(), }, - sub_plan: "1000".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "1000".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), num_gifted_months: 1, } ) @@ -746,12 +741,12 @@ mod tests { is_sender_anonymous: true, cumulative_months: 13, recipient: TwitchUserBasics { - id: "151784015".to_owned(), - login: "dot0422".to_owned(), - name: "Dot0422".to_owned(), + id: "151784015".into(), + login: "dot0422".into(), + name: "Dot0422".into(), }, - sub_plan: "1000".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "1000".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), num_gifted_months: 1, } ) @@ -771,12 +766,12 @@ mod tests { is_sender_anonymous: true, cumulative_months: 2, recipient: TwitchUserBasics { - id: "236653628".to_owned(), - login: "qatarking24xd".to_owned(), - name: "qatarking24xd".to_owned(), + id: "236653628".into(), + login: "qatarking24xd".into(), + name: "qatarking24xd".into(), }, - sub_plan: "1000".to_owned(), - sub_plan_name: "Channel Subscription (xqcow)".to_owned(), + sub_plan: "1000".into(), + sub_plan_name: "Channel Subscription (xqcow)".into(), num_gifted_months: 1, } ) @@ -793,7 +788,7 @@ mod tests { UserNoticeEvent::SubMysteryGift { mass_gift_count: 20, sender_total_gifts: 100, - sub_plan: "1000".to_owned(), + sub_plan: "1000".into(), } ) } @@ -808,7 +803,7 @@ mod tests { msg.event, UserNoticeEvent::AnonSubMysteryGift { mass_gift_count: 10, - sub_plan: "1000".to_owned(), + sub_plan: "1000".into(), } ) } @@ -825,7 +820,7 @@ mod tests { msg.event, UserNoticeEvent::AnonSubMysteryGift { mass_gift_count: 15, - sub_plan: "2000".to_owned(), + sub_plan: "2000".into(), } ) } @@ -840,8 +835,8 @@ mod tests { assert_eq!( msg.event, UserNoticeEvent::GiftPaidUpgrade { - gifter_login: "stridezgum".to_owned(), - gifter_name: "Stridezgum".to_owned(), + gifter_login: "stridezgum".into(), + gifter_name: "Stridezgum".into(), promotion: None, } ) @@ -859,10 +854,10 @@ mod tests { assert_eq!( msg.event, UserNoticeEvent::GiftPaidUpgrade { - gifter_login: "stridezgum".to_owned(), - gifter_name: "Stridezgum".to_owned(), + gifter_login: "stridezgum".into(), + gifter_name: "Stridezgum".into(), promotion: Some(SubGiftPromo { - promo_name: "TestSubtember2020".to_owned(), + promo_name: "TestSubtember2020".into(), total_gifts: 4003, }), } @@ -894,7 +889,7 @@ mod tests { msg.event, UserNoticeEvent::AnonGiftPaidUpgrade { promotion: Some(SubGiftPromo { - promo_name: "TestSubtember2020".to_owned(), + promo_name: "TestSubtember2020".into(), total_gifts: 4003, }) } @@ -910,7 +905,7 @@ mod tests { assert_eq!( msg.event, UserNoticeEvent::Ritual { - ritual_name: "new_chatter".to_owned() + ritual_name: "new_chatter".into() } ) } @@ -946,25 +941,25 @@ mod tests { assert_eq!( msg.message_text, - Some("ACTION Kappa TEST TEST Kappa :)".to_owned()) + Some("ACTION Kappa TEST TEST Kappa :)".into()) ); assert_eq!( msg.emotes, vec![ Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 7, end: 12 }, - code: " Kapp".to_owned(), + code: " Kapp".into(), }, Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 23, end: 28 }, - code: " Kapp".to_owned(), + code: " Kapp".into(), }, Emote { - id: "499".to_owned(), + id: "499".into(), char_range: Range { start: 29, end: 31 }, - code: " :".to_owned(), + code: " :".into(), }, ] ) diff --git a/src/message/commands/userstate.rs b/src/message/commands/userstate.rs index 634b3f6..86f8d78 100644 --- a/src/message/commands/userstate.rs +++ b/src/message/commands/userstate.rs @@ -87,11 +87,11 @@ mod tests { assert_eq!( msg, UserStateMessage { - channel_login: "randers".to_owned(), - user_name: "TESTUSER".to_owned(), + channel_login: "randers".into(), + user_name: "TESTUSER".into(), badge_info: vec![], badges: vec![], - emote_sets: vec!["0".to_owned()].into_iter().collect(), + emote_sets: vec!["0".into()].into_iter().collect(), name_color: Some(RGBColor { r: 0xFF, g: 0x00, @@ -111,16 +111,16 @@ mod tests { assert_eq!( msg, UserStateMessage { - channel_login: "randers".to_owned(), - user_name: "TESTUSER".to_owned(), + channel_login: "randers".into(), + user_name: "TESTUSER".into(), badge_info: vec![], badges: vec![Badge { - name: "moderator".to_owned(), - version: "1".to_owned() + name: "moderator".into(), + version: "1".into() }], emote_sets: vec![ - "0".to_owned(), - "75c09c7b-332a-43ec-8be8-1d4571706155".to_owned() + "0".into(), + "75c09c7b-332a-43ec-8be8-1d4571706155".into() ] .into_iter() .collect(), diff --git a/src/message/commands/whisper.rs b/src/message/commands/whisper.rs index ca22c94..c66ebb8 100644 --- a/src/message/commands/whisper.rs +++ b/src/message/commands/whisper.rs @@ -86,13 +86,13 @@ mod tests { assert_eq!( msg, WhisperMessage { - recipient_login: "randers811".to_owned(), + recipient_login: "randers811".into(), sender: TwitchUserBasics { - id: "40286300".to_owned(), - login: "randers".to_owned(), - name: "randers".to_owned() + id: "40286300".into(), + login: "randers".into(), + name: "randers".into() }, - message_text: "hello, this is a test Kappa".to_owned(), + message_text: "hello, this is a test Kappa".into(), name_color: Some(RGBColor { r: 0x19, g: 0xE6, @@ -100,9 +100,9 @@ mod tests { }), badges: vec![], emotes: vec![Emote { - id: "25".to_owned(), + id: "25".into(), char_range: Range { start: 22, end: 27 }, - code: "Kappa".to_owned() + code: "Kappa".into() }], source: irc_message }, diff --git a/src/message/mod.rs b/src/message/mod.rs index 7ff6d10..1403aaf 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -130,7 +130,7 @@ pub struct IRCMessage { /// let msg = irc!["PRIVMSG", "#sodapoppin", "Hello guys!"]; /// /// assert_eq!(msg.command, "PRIVMSG"); -/// assert_eq!(msg.params, vec!["#sodapoppin".to_owned(), "Hello guys!".to_owned()]); +/// assert_eq!(msg.params, vec!["#sodapoppin".into(), "Hello guys!".into()]); /// assert_eq!(msg.as_raw_irc(), "PRIVMSG #sodapoppin :Hello guys!"); /// # } /// ``` @@ -314,30 +314,30 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "display-name".to_owned() => "randers".to_owned(), - "tmi-sent-ts" .to_owned() => "1577040814959".to_owned(), - "historical".to_owned() => "1".to_owned(), - "room-id".to_owned() => "11148817".to_owned(), - "emotes".to_owned() => "".to_owned(), - "color".to_owned() => "#19E6E6".to_owned(), - "id".to_owned() => "6e2ccb1f-01ed-44d0-85b6-edf762524475".to_owned(), - "turbo".to_owned() => "0".to_owned(), - "flags".to_owned() => "".to_owned(), - "user-id".to_owned() => "40286300".to_owned(), - "rm-received-ts".to_owned() => "1577040815136".to_owned(), - "user-type".to_owned() => "mod".to_owned(), - "subscriber".to_owned() => "1".to_owned(), - "badges".to_owned() => "moderator/1,subscriber/12".to_owned(), - "badge-info".to_owned() => "subscriber/16".to_owned(), - "mod".to_owned() => "1".to_owned(), + "display-name".into() => "randers".into(), + "tmi-sent-ts" .into() => "1577040814959".into(), + "historical".into() => "1".into(), + "room-id".into() => "11148817".into(), + "emotes".into() => "".into(), + "color".into() => "#19E6E6".into(), + "id".into() => "6e2ccb1f-01ed-44d0-85b6-edf762524475".into(), + "turbo".into() => "0".into(), + "flags".into() => "".into(), + "user-id".into() => "40286300".into(), + "rm-received-ts".into() => "1577040815136".into(), + "user-type".into() => "mod".into(), + "subscriber".into() => "1".into(), + "badges".into() => "moderator/1,subscriber/12".into(), + "badge-info".into() => "subscriber/16".into(), + "mod".into() => "1".into(), }), prefix: Some(IRCPrefix::Full { - nick: "randers".to_owned(), - user: Some("randers".to_owned()), - host: Some("randers.tmi.twitch.tv".to_owned()), + nick: "randers".into(), + user: Some("randers".into()), + host: Some("randers.tmi.twitch.tv".into()), }), - command: "PRIVMSG".to_owned(), - params: vec!["#pajlada".to_owned(), "Pajapains".to_owned()], + command: "PRIVMSG".into(), + params: vec!["#pajlada".into(), "Pajapains".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -352,10 +352,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "coolguy".to_owned() + host: "coolguy".into() }), - command: "FOO".to_owned(), - params: vec!["bar".to_owned(), "baz".to_owned(), "asdf".to_owned()], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into(), "asdf".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -370,8 +370,8 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: None, - command: "FOO".to_owned(), - params: vec!["bar".to_owned(), "baz".to_owned(), ":asdf".to_owned()], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into(), ":asdf".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -386,14 +386,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "coolguy".to_owned() + host: "coolguy".into() }), - command: "FOO".to_owned(), - params: vec![ - "bar".to_owned(), - "baz".to_owned(), - " asdf quux ".to_owned() - ], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into(), " asdf quux ".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -408,10 +404,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "coolguy".to_owned() + host: "coolguy".into() }), - command: "PRIVMSG".to_owned(), - params: vec!["bar".to_owned(), "lol :) ".to_owned()], + command: "PRIVMSG".into(), + params: vec!["bar".into(), "lol :) ".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -426,10 +422,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "coolguy".to_owned() + host: "coolguy".into() }), - command: "FOO".to_owned(), - params: vec!["bar".to_owned(), "baz".to_owned(), "".to_owned()], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into(), "".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -444,10 +440,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "coolguy".to_owned() + host: "coolguy".into() }), - command: "FOO".to_owned(), - params: vec!["bar".to_owned(), "baz".to_owned(), " ".to_owned()], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into(), " ".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -461,13 +457,13 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "a".to_owned() => "b".to_owned(), - "c".to_owned() => "32".to_owned(), - "k".to_owned() => "".to_owned(), - "rt".to_owned() => "ql7".to_owned() + "a".into() => "b".into(), + "c".into() => "32".into(), + "k".into() => "".into(), + "rt".into() => "ql7".into() }), prefix: None, - command: "FOO".to_owned(), + command: "FOO".into(), params: vec![], } ); @@ -482,12 +478,12 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "a".to_owned() => "b\\and\nk".to_owned(), - "c".to_owned() => "72 45".to_owned(), - "d".to_owned() => "gh;764".to_owned(), + "a".into() => "b\\and\nk".into(), + "c".into() => "72 45".into(), + "d".into() => "gh;764".into(), }), prefix: None, - command: "FOO".to_owned(), + command: "FOO".into(), params: vec![], } ); @@ -502,15 +498,15 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "c".to_owned() => "".to_owned(), - "h".to_owned() => "".to_owned(), - "a".to_owned() => "b".to_owned(), + "c".into() => "".into(), + "h".into() => "".into(), + "a".into() => "b".into(), }), prefix: Some(IRCPrefix::HostOnly { - host: "quux".to_owned() + host: "quux".into() }), - command: "AB".to_owned(), - params: vec!["cd".to_owned()], + command: "AB".into(), + params: vec!["cd".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -524,11 +520,9 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! {}), - prefix: Some(IRCPrefix::HostOnly { - host: "src".to_owned() - }), - command: "JOIN".to_owned(), - params: vec!["#chan".to_owned()], + prefix: Some(IRCPrefix::HostOnly { host: "src".into() }), + command: "JOIN".into(), + params: vec!["#chan".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -550,10 +544,8 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! {}), - prefix: Some(IRCPrefix::HostOnly { - host: "src".to_owned() - }), - command: "AWAY".to_owned(), + prefix: Some(IRCPrefix::HostOnly { host: "src".into() }), + command: "AWAY".into(), params: vec![], } ); @@ -569,10 +561,10 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "cool\tguy".to_owned() + host: "cool\tguy".into() }), - command: "FOO".to_owned(), - params: vec!["bar".to_owned(), "baz".to_owned()], + command: "FOO".into(), + params: vec!["bar".into(), "baz".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -587,12 +579,12 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::Full { - nick: "coolguy".to_owned(), - user: Some("~ag".to_owned()), - host: Some("n\u{0002}et\u{0003}05w\u{000f}ork.admin".to_owned()) + nick: "coolguy".into(), + user: Some("~ag".into()), + host: Some("n\u{0002}et\u{0003}05w\u{000f}ork.admin".into()) }), - command: "PRIVMSG".to_owned(), - params: vec!["foo".to_owned(), "bar baz".to_owned()], + command: "PRIVMSG".into(), + params: vec!["foo".into(), "bar baz".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -606,20 +598,16 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "tag1".to_owned() => "value1".to_owned(), - "tag2".to_owned() => "".to_owned(), - "vendor1/tag3".to_owned() => "value2".to_owned(), - "vendor2/tag4".to_owned() => "".to_owned() + "tag1".into() => "value1".into(), + "tag2".into() => "".into(), + "vendor1/tag3".into() => "value2".into(), + "vendor2/tag4".into() => "".into() }), prefix: Some(IRCPrefix::HostOnly { - host: "irc.example.com".to_owned() + host: "irc.example.com".into() }), - command: "COMMAND".to_owned(), - params: vec![ - "param1".to_owned(), - "param2".to_owned(), - "param3 param3".to_owned() - ], + command: "COMMAND".into(), + params: vec!["param1".into(), "param2".into(), "param3 param3".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -633,13 +621,13 @@ mod tests { message, IRCMessage { tags: IRCTags::from(hashmap! { - "display-name".to_owned() => "테스트계정420".to_owned(), + "display-name".into() => "테스트계정420".into(), }), prefix: Some(IRCPrefix::HostOnly { - host: "tmi.twitch.tv".to_owned() + host: "tmi.twitch.tv".into() }), - command: "PRIVMSG".to_owned(), - params: vec!["#pajlada".to_owned(), "test".to_owned(),], + command: "PRIVMSG".into(), + params: vec!["#pajlada".into(), "test".into(),], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -654,8 +642,8 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: None, - command: "PING".to_owned(), - params: vec!["tmi.twitch.tv".to_owned()], + command: "PING".into(), + params: vec!["tmi.twitch.tv".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -670,9 +658,9 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: Some(IRCPrefix::HostOnly { - host: "tmi.twitch.tv".to_owned() + host: "tmi.twitch.tv".into() }), - command: "PING".to_owned(), + command: "PING".into(), params: vec![], } ); @@ -779,8 +767,8 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: None, - command: "PING".to_owned(), - params: vec!["asd".to_owned(), "def".to_owned(), "".to_owned()], + command: "PING".into(), + params: vec!["asd".into(), "def".into(), "".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -795,8 +783,8 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: None, - command: "PING".to_owned(), - params: vec!["".to_owned()], + command: "PING".into(), + params: vec!["".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -811,8 +799,8 @@ mod tests { IRCMessage { tags: IRCTags::from(hashmap! {}), prefix: None, - command: "500".to_owned(), - params: vec!["Internal Server Error".to_owned()], + command: "500".into(), + params: vec!["Internal Server Error".into()], } ); assert_eq!(IRCMessage::parse(&message.as_raw_irc()).unwrap(), message); @@ -854,7 +842,7 @@ mod tests { IRCMessage { tags: IRCTags::new(), prefix: None, - command: "PRIVMSG".to_owned(), + command: "PRIVMSG".into(), params: vec![], } ); @@ -863,8 +851,8 @@ mod tests { IRCMessage { tags: IRCTags::new(), prefix: None, - command: "PRIVMSG".to_owned(), - params: vec!["#pajlada".to_owned()], + command: "PRIVMSG".into(), + params: vec!["#pajlada".into()], } ); assert_eq!( @@ -872,8 +860,8 @@ mod tests { IRCMessage { tags: IRCTags::new(), prefix: None, - command: "PRIVMSG".to_owned(), - params: vec!["#pajlada".to_owned(), "LUL xD".to_owned()], + command: "PRIVMSG".into(), + params: vec!["#pajlada".into(), "LUL xD".into()], } ); } diff --git a/src/message/twitch.rs b/src/message/twitch.rs index 80b579a..adec147 100644 --- a/src/message/twitch.rs +++ b/src/message/twitch.rs @@ -189,16 +189,16 @@ mod tests { pub fn test_reply_to_message_trait_impl() { // just making sure that DeleteMessage is implemented for all of these variants let _a: Box = Box::new(("asd", "def")); - let _b: Box = Box::new(("asd".to_owned(), "def")); - let _c: Box = Box::new(("asd", "def".to_owned())); - let d: Box = Box::new(("asd".to_owned(), "def".to_owned())); + let _b: Box = Box::new(("asd".into(), "def")); + let _c: Box = Box::new(("asd", "def".into())); + let d: Box = Box::new(("asd".into(), "def".into())); assert_eq!(d.channel_login(), "asd"); assert_eq!(d.message_id(), "def"); } fn function_with_impl_arg(a: &impl ReplyToMessage) -> FastStr { - a.message_id().to_owned() + FastStr::from_ref(a.message_id()) } #[test] From c1acb9033a0f4add3d2e1c86fc52b00bd758a6d0 Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:32:47 +0300 Subject: [PATCH 6/8] fix missing import --- src/message/twitch.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/message/twitch.rs b/src/message/twitch.rs index adec147..3a20281 100644 --- a/src/message/twitch.rs +++ b/src/message/twitch.rs @@ -182,6 +182,8 @@ where #[cfg(test)] mod tests { + use fast_str::FastStr; + use crate::message::{IRCMessage, PrivmsgMessage, ReplyToMessage}; use std::convert::TryFrom; From 2570e64dd17d77a45403269b547a6c4e51d329f9 Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:35:27 +0300 Subject: [PATCH 7/8] fix some misc stuff --- src/message/twitch.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/message/twitch.rs b/src/message/twitch.rs index 3a20281..c3e0be3 100644 --- a/src/message/twitch.rs +++ b/src/message/twitch.rs @@ -148,7 +148,7 @@ pub struct ReplyParent { /// Extract the `message_id` from a [`PrivmsgMessage`](crate::message::PrivmsgMessage) or directly /// use an arbitrary [`FastStr`] or [`&str`] as a message ID. This trait allows you to plug both -/// of these types directly into [`say_in_reply_to()`](crate::TwitchIRCClient::say_in_reply_to) +/// of these types directly to_owned() [`say_in_reply_to()`](crate::TwitchIRCClient::say_in_reply_to) /// for your convenience. /// /// For tuples `(&str, &str)` or `(FastStr, FastStr)`, the first member is the login name @@ -191,9 +191,9 @@ mod tests { pub fn test_reply_to_message_trait_impl() { // just making sure that DeleteMessage is implemented for all of these variants let _a: Box = Box::new(("asd", "def")); - let _b: Box = Box::new(("asd".into(), "def")); - let _c: Box = Box::new(("asd", "def".into())); - let d: Box = Box::new(("asd".into(), "def".into())); + let _b: Box = Box::new(("asd".to_owned(), "def")); + let _c: Box = Box::new(("asd", "def".to_owned())); + let d: Box = Box::new(("asd".to_owned(), "def".to_owned())); assert_eq!(d.channel_login(), "asd"); assert_eq!(d.message_id(), "def"); From 55c50c300ccf308b0d257383a221a1336e78543e Mon Sep 17 00:00:00 2001 From: AbstractiveNord <109909821+AbstractiveNord@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:36:29 +0300 Subject: [PATCH 8/8] fix missing match path --- src/message/commands/notice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message/commands/notice.rs b/src/message/commands/notice.rs index af947dd..3c6c2ef 100644 --- a/src/message/commands/notice.rs +++ b/src/message/commands/notice.rs @@ -49,7 +49,7 @@ impl TryFrom for NoticeMessage { message_id: { match source.try_get_optional_nonempty_tag_value("msg-id")? { Some(message_id) => Some(FastStr::from_ref(message_id)), - None => todo!(), + None => None, } }, source,