From 0fd5f4798257e05b937fadcd7781825030d73a4b Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:12:13 +0200 Subject: [PATCH] Add option to check if users leave or join Twitch chat --- data/locale/en-US.ini | 8 +++ plugins/twitch/chat-connection.cpp | 43 ++++++++++++-- plugins/twitch/chat-connection.hpp | 3 + plugins/twitch/macro-condition-twitch.cpp | 71 ++++++++++++++++++++++- plugins/twitch/macro-condition-twitch.hpp | 3 + 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 2b3a0a89..c88f4dab 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -713,6 +713,8 @@ AdvSceneSwitcher.condition.twitch.type.chat.properties.color="Color matches" AdvSceneSwitcher.condition.twitch.type.chat.properties.displayName="Display name matches" AdvSceneSwitcher.condition.twitch.type.chat.properties.loginName="User name matches" AdvSceneSwitcher.condition.twitch.type.chat.properties.badge="User has badge" +AdvSceneSwitcher.condition.twitch.type.chat.userJoined="User joined the chat" +AdvSceneSwitcher.condition.twitch.type.chat.userLeft="User left the chat" AdvSceneSwitcher.condition.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" AdvSceneSwitcher.condition.twitch.entry="Channel{{channel}}{{conditions}}{{pointsReward}}{{streamTitle}}{{regex}}{{category}}" AdvSceneSwitcher.condition.twitch.entry.account="Check using account{{account}}" @@ -1765,6 +1767,12 @@ AdvSceneSwitcher.tempVar.twitch.is_turbo.chatReceive.description="'true' if the AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive="Is chatter a VIP" AdvSceneSwitcher.tempVar.twitch.is_vip.chatReceive.description="'true' if the chatter is a VIP in the channel, 'false' otherwise." +AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin="User login" +AdvSceneSwitcher.tempVar.twitch.user_login.chatJoin.description="The user login of the person who joined the chat room." + +AdvSceneSwitcher.tempVar.twitch.user_login.chatLeave="User login" +AdvSceneSwitcher.tempVar.twitch.user_login.chatLeave.description="The user login of the person who left the chat room." + AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid="Raid creator user ID" AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid.description="The broadcaster ID that created the raid." AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid="Raid creator user login" diff --git a/plugins/twitch/chat-connection.cpp b/plugins/twitch/chat-connection.cpp index f2149484..b97315f1 100644 --- a/plugins/twitch/chat-connection.cpp +++ b/plugins/twitch/chat-connection.cpp @@ -181,8 +181,6 @@ static void parseCommand(const std::string &rawCommandComponent, return; } else if (message.command.command == "PING" || message.command.command == "001" || - message.command.command == "JOIN" || - message.command.command == "PART" || message.command.command == "NOTICE" || message.command.command == "CLEARCHAT" || message.command.command == "HOSTTARGET" || @@ -191,6 +189,18 @@ static void parseCommand(const std::string &rawCommandComponent, return; } message.command.parameters = commandParts[1]; + } else if (message.command.command == "JOIN") { + if (commandParts.size() < 2) { + return; + } + message.properties.joinedChannel = true; + message.command.parameters = commandParts[1]; + } else if (message.command.command == "PART") { + if (commandParts.size() < 2) { + return; + } + message.properties.leftChannel = true; + message.command.parameters = commandParts[1]; } else if (message.command.command == "GLOBALUSERSTATE" || message.command.command == "USERSTATE" || message.command.command == "ROOMSTATE" || @@ -504,12 +514,34 @@ void TwitchChatConnection::JoinChannel(const std::string &channelName) Send("JOIN #" + channelName); } +static bool nickMatchesTokenUser(const std::string &nick, TwitchToken &token) +{ + auto tokenUserName = token.GetName(); + std::transform(tokenUserName.begin(), tokenUserName.end(), + tokenUserName.begin(), + [](unsigned char c) { return std::tolower(c); }); + return nick == tokenUserName; +} + void TwitchChatConnection::HandleJoin(const IRCMessage &message) { + if (!nickMatchesTokenUser(message.source.nick, _token)) { + _messageDispatcher.DispatchMessage(message); + return; + } _joinedChannelName = std::get(message.command.parameters); vblog(LOG_INFO, "Twitch chat join was successful!"); } +void TwitchChatConnection::HandlePart(const IRCMessage &message) +{ + if (!nickMatchesTokenUser(message.source.nick, _token)) { + _messageDispatcher.DispatchMessage(message); + return; + } + vblog(LOG_INFO, "Left Twitch chat!"); +} + void TwitchChatConnection::HandleNewMessage(const IRCMessage &message) { _messageDispatcher.DispatchMessage(message); @@ -571,7 +603,8 @@ void TwitchChatConnection::OnMessage( { static constexpr std::string_view authOKCommand = "001"; static constexpr std::string_view pingCommand = "PING"; - static constexpr std::string_view joinOKCommand = "JOIN"; + static constexpr std::string_view joinCommand = "JOIN"; + static constexpr std::string_view partCommand = "PART"; static constexpr std::string_view noticeCommand = "NOTICE"; static constexpr std::string_view reconnectCommand = "RECONNECT"; static constexpr std::string_view newMessageCommand = "PRIVMSG"; @@ -596,8 +629,10 @@ void TwitchChatConnection::OnMessage( } else if (message.command.command == pingCommand) { Send("PONG " + std::get(message.command.parameters)); - } else if (message.command.command == joinOKCommand) { + } else if (message.command.command == joinCommand) { HandleJoin(message); + } else if (message.command.command == partCommand) { + HandlePart(message); } else if (message.command.command == newMessageCommand) { HandleNewMessage(message); } else if (message.command.command == whisperCommand) { diff --git a/plugins/twitch/chat-connection.hpp b/plugins/twitch/chat-connection.hpp index 6e2e0f8a..03548bd7 100644 --- a/plugins/twitch/chat-connection.hpp +++ b/plugins/twitch/chat-connection.hpp @@ -44,6 +44,8 @@ struct IRCMessage { std::string userId; std::string userType; bool isVIP = false; + bool joinedChannel = false; + bool leftChannel = false; } properties; struct { @@ -95,6 +97,7 @@ private: void Authenticate(); void JoinChannel(const std::string &); void HandleJoin(const IRCMessage &); + void HandlePart(const IRCMessage &); void HandleNewMessage(const IRCMessage &); void HandleWhisper(const IRCMessage &); void HandleNotice(const IRCMessage &) const; diff --git a/plugins/twitch/macro-condition-twitch.cpp b/plugins/twitch/macro-condition-twitch.cpp index 514a49fd..11141037 100644 --- a/plugins/twitch/macro-condition-twitch.cpp +++ b/plugins/twitch/macro-condition-twitch.cpp @@ -121,6 +121,10 @@ const static std::map conditionTyp "AdvSceneSwitcher.condition.twitch.type.polling.channel.category"}, {MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED, "AdvSceneSwitcher.condition.twitch.type.chat.message"}, + {MacroConditionTwitch::Condition::CHAT_USER_JOINED, + "AdvSceneSwitcher.condition.twitch.type.chat.userJoined"}, + {MacroConditionTwitch::Condition::CHAT_USER_LEFT, + "AdvSceneSwitcher.condition.twitch.type.chat.userLeft"}, }; const static std::map eventIdentifiers = { @@ -385,6 +389,12 @@ bool MacroConditionTwitch::CheckChatMessages(TwitchToken &token) continue; } + // Join and leave message don't have any message data + if (message->properties.leftChannel || + message->properties.joinedChannel) { + continue; + } + if (!_chatMessagePattern.Matches(*message)) { continue; } @@ -443,6 +453,41 @@ bool MacroConditionTwitch::CheckChatMessages(TwitchToken &token) return false; } +bool MacroConditionTwitch::CheckChatUserJoinOrLeave(TwitchToken &token) +{ + if (!_chatConnection) { + _chatConnection = TwitchChatConnection::GetChatConnection( + token, _channel); + if (!_chatConnection) { + return false; + } + _chatBuffer = _chatConnection->RegisterForMessages(); + return false; + } + + while (!_chatBuffer->Empty()) { + auto message = _chatBuffer->ConsumeMessage(); + if (!message) { + continue; + } + + if ((_condition == Condition::CHAT_USER_JOINED && + !message->properties.joinedChannel) || + (_condition == Condition::CHAT_USER_LEFT && + !message->properties.leftChannel)) { + continue; + } + + SetTempVarValue("user_login", message->source.nick); + + if (_clearBufferOnMatch) { + _eventBuffer->Clear(); + } + return true; + } + return false; +} + void MacroConditionTwitch::SetTempVarValues(const ChannelLiveInfo &info) { SetTempVarValue("broadcaster_user_id", info.user_id); @@ -625,6 +670,14 @@ bool MacroConditionTwitch::CheckCondition() } return CheckChatMessages(*token); } + case Condition::CHAT_USER_JOINED: + case Condition::CHAT_USER_LEFT: { + auto token = _token.lock(); + if (!token) { + return false; + } + return CheckChatUserJoinOrLeave(*token); + } default: break; } @@ -773,6 +826,10 @@ bool MacroConditionTwitch::ConditionIsSupportedByToken() {Condition::CATEGORY_POLLING, {}}, {Condition::CHAT_MESSAGE_RECEIVED, {{"chat:read"}, {"chat:edit"}}}, + {Condition::CHAT_USER_JOINED, + {{"chat:read"}, {"chat:edit"}}}, + {Condition::CHAT_USER_LEFT, + {{"chat:read"}, {"chat:edit"}}}, }; auto token = _token.lock(); @@ -967,6 +1024,8 @@ void MacroConditionTwitch::SetupTempVars() }; if (_condition != Condition::CHAT_MESSAGE_RECEIVED && + _condition != Condition::CHAT_USER_JOINED && + _condition != Condition::CHAT_USER_LEFT && _condition != Condition::RAID_INBOUND_EVENT && _condition != Condition::RAID_OUTBOUND_EVENT) { setupTempVarHelper("broadcaster_user_id"); @@ -1303,6 +1362,12 @@ void MacroConditionTwitch::SetupTempVars() setupTempVarHelper("is_turbo", ".chatReceive"); setupTempVarHelper("is_vip", ".chatReceive"); break; + case Condition::CHAT_USER_JOINED: + setupTempVarHelper("user_login", ".chatJoin"); + break; + case Condition::CHAT_USER_LEFT: + setupTempVarHelper("user_login", ".chatLeave"); + break; default: break; } @@ -1546,7 +1611,11 @@ void MacroConditionTwitchEdit::SetWidgetVisibility() _clearBufferOnMatch->setVisible( _entryData->IsUsingEventSubCondition() || _entryData->GetCondition() == - MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED); + MacroConditionTwitch::Condition::CHAT_MESSAGE_RECEIVED || + _entryData->GetCondition() == + MacroConditionTwitch::Condition::CHAT_USER_JOINED || + _entryData->GetCondition() == + MacroConditionTwitch::Condition::CHAT_USER_LEFT); if (condition == MacroConditionTwitch::Condition::TITLE_POLLING) { RemoveStretchIfPresent(_layout); diff --git a/plugins/twitch/macro-condition-twitch.hpp b/plugins/twitch/macro-condition-twitch.hpp index 3a0c2e6d..c500591e 100644 --- a/plugins/twitch/macro-condition-twitch.hpp +++ b/plugins/twitch/macro-condition-twitch.hpp @@ -74,6 +74,8 @@ public: // Chat CHAT_MESSAGE_RECEIVED = 500000, + CHAT_USER_JOINED = 500100, + CHAT_USER_LEFT = 500200, // Polling LIVE_POLLING = 1000000, @@ -110,6 +112,7 @@ private: bool CheckChannelGenericEvents(); bool CheckChannelLiveEvents(); bool CheckChatMessages(TwitchToken &token); + bool CheckChatUserJoinOrLeave(TwitchToken &token); void RegisterEventSubscription(); void ResetSubscription();