Skip to content

Commit

Permalink
Reuse the same chat connection across multiple conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
WarmUpTill committed Nov 23, 2023
1 parent 48bcdb8 commit 8af5686
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 75 deletions.
5 changes: 5 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,11 @@ AdvSceneSwitcher.tempVar.twitch.user_login.removeMod.description="The user login
AdvSceneSwitcher.tempVar.twitch.user_name.removeMod="User name"
AdvSceneSwitcher.tempVar.twitch.user_name.removeMod.description="The display name of the removed moderator."

AdvSceneSwitcher.tempVar.twitch.chatter="User login"
AdvSceneSwitcher.tempVar.twitch.chatter.description="The user login of the person who sent the chat message."
AdvSceneSwitcher.tempVar.twitch.chat_message="Chat message"


AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
AdvSceneSwitcher.tempVar.audio.configured_volume="Configured volume"
Expand Down
4 changes: 2 additions & 2 deletions src/macro-external/twitch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ target_sources(
category-selection.hpp
channel-selection.cpp
channel-selection.hpp
chat-helpers.cpp
chat-helpers.hpp
chat-connection.cpp
chat-connection.hpp
event-sub.cpp
event-sub.hpp
macro-action-twitch.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "chat-helpers.hpp"
#include "chat-connection.hpp"
#include "token.hpp"
#include "twitch-helpers.hpp"

Expand All @@ -16,31 +16,6 @@ const int messageIDBufferSize = 20;

/* ------------------------------------------------------------------------- */

using BadgeMap = std::unordered_map<std::string, std::string>;
using EmoteMap =
std::unordered_map<std::string, std::vector<std::pair<int, int>>>;
using EmoteSet = std::vector<std::string>;

struct ParsedTags {
std::unordered_map<std::string, std::variant<std::string, BadgeMap,
EmoteMap, EmoteSet>>
tagMap;
};

struct IRCMessage {
ParsedTags tags;
struct {
std::string nick;
std::string host;
} source;
struct {
std::string command;
std::variant<std::string, bool, std::vector<std::string>>
parameters;
} command;
std::string message;
};

static ParsedTags parseTags(const std::string &tags)
{
ParsedTags parsedTags;
Expand Down Expand Up @@ -75,7 +50,7 @@ static ParsedTags parseTags(const std::string &tags)
}

if (tagName == "badges" || tagName == "badge-info") {
BadgeMap badgeMap;
ParsedTags::BadgeMap badgeMap;
std::vector<std::string> badgePairs;
size_t badgePos = 0;
size_t badgeEndPos;
Expand Down Expand Up @@ -104,7 +79,7 @@ static ParsedTags parseTags(const std::string &tags)
parsedTags.tagMap[tagName] = badgeMap;

} else if (tagName == "emotes") {
EmoteMap emoteMap;
ParsedTags::EmoteMap emoteMap;
std::vector<std::string> emotePairs;
size_t emotePos = 0;
size_t emoteEndPos;
Expand Down Expand Up @@ -157,7 +132,7 @@ static ParsedTags parseTags(const std::string &tags)

parsedTags.tagMap[tagName] = emoteMap;
} else if (tagName == "emote-sets") {
EmoteSet emoteSetIds;
ParsedTags::EmoteSet emoteSetIds;
size_t setIdPos = 0;
size_t setIdEndPos;

Expand Down Expand Up @@ -349,7 +324,12 @@ std::vector<IRCMessage> parseMessages(const std::string &messagesStr)

constexpr std::string_view defaultURL = "wss://irc-ws.chat.twitch.tv:443";

TwitchChatConnection::TwitchChatConnection() : QObject(nullptr)
std::map<TwitchChatConnection::ChatMapKey, std::weak_ptr<TwitchChatConnection>>
TwitchChatConnection::_chatMap = {};

TwitchChatConnection::TwitchChatConnection(const TwitchToken &token,
const TwitchChannel &channel)
: QObject(nullptr), _token(token), _channel(channel)
{
_client.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
Expand Down Expand Up @@ -411,6 +391,30 @@ void TwitchChatConnection::UnregisterInstance()
_instances.erase(it, _instances.end());
}

std::shared_ptr<TwitchChatConnection>
TwitchChatConnection::GetChatConnection(const TwitchToken &token_,
const TwitchChannel &channel)
{
auto token = token_.GetToken();
if (!token ||
!token_.AnyOptionIsEnabled({{"chat:read"}, {"chat:edit"}}) ||
channel.GetName().empty()) {
return {};
}
auto key = ChatMapKey{*token, channel.GetName()};
auto it = _chatMap.find(key);
if (it != _chatMap.end()) {
auto connection = it->second.lock();
if (connection) {
return connection;
}
}
auto connection = std::shared_ptr<TwitchChatConnection>(
new TwitchChatConnection(token_, channel));
_chatMap[key] = connection;
return connection;
}

void TwitchChatConnection::ClearAllMessages()
{
std::lock_guard<std::mutex> lock(_instancesMtx);
Expand Down Expand Up @@ -445,14 +449,6 @@ void TwitchChatConnection::ConnectThread()
_connected = false;
}

void TwitchChatConnection::SetToken(const std::weak_ptr<TwitchToken> &token)
{
// TODO: Reset connection
//Disconnect();
//_authenticated = false;
_token = token;
}

void TwitchChatConnection::Connect()
{
std::lock_guard<std::mutex> lock(_connectMtx);
Expand Down Expand Up @@ -505,22 +501,21 @@ static std::string toLowerCase(const std::string &str)
return result;
}

std::vector<std::string>
TwitchChatConnection::Messages(const TwitchChannel &channel)
std::vector<IRCMessage> TwitchChatConnection::Messages()
{
std::lock_guard<std::mutex> lock(_messageMtx);
if (!_authenticated) {
Connect();
return {};
}

if (_joinedChannelName !=
"#" + toLowerCase(std::string(channel.GetName()))) {
"#" + toLowerCase(std::string(_channel.GetName()))) {
// TODO: disconnect previous channel?
JoinChannel(channel.GetName());
JoinChannel(_channel.GetName());
return {};
}

std::lock_guard<std::mutex> lock(_messageMtx);
return _messages;
}

Expand Down Expand Up @@ -549,20 +544,19 @@ static obs_data_t *copyData(const OBSData &data)

void TwitchChatConnection::Authenticate()
{
auto token = _token.lock();
if (!token) {
if (!_token.GetToken()) {
blog(LOG_INFO,
"Joining Twitch chat failed due to missing token!");
}

Send("CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands");
auto pass = token->GetToken();
auto pass = _token.GetToken();
if (!pass.has_value()) {
blog(LOG_INFO,
"Joining Twitch chat failed due to invalid token!");
}
Send("PASS oauth:" + *pass);
Send("NICK " + token->GetName());
Send("NICK " + _token.GetName());
}

void TwitchChatConnection::JoinChannel(const std::string &channelName)
Expand All @@ -583,11 +577,21 @@ void TwitchChatConnection::HandleJoin(const IRCMessage &message)
void TwitchChatConnection::HandleNewMessage(const IRCMessage &message)
{
std::lock_guard<std::mutex> lock(_messageMtx);
_messages.emplace_back(message.message);
_messages.emplace_back(message);
vblog(LOG_INFO, "Received new chat message %s",
message.message.c_str());
}

void TwitchChatConnection::HandleNotice(const IRCMessage &message) const
{
if (message.message == "Login unsuccessful") {
blog(LOG_INFO, "Twitch chat connection was unsuccessful: %s",
message.message.c_str());
return;
}
vblog(LOG_INFO, "Twitch chat notice: %s", message.message.c_str());
}

void TwitchChatConnection::OnOpen(connection_hdl)
{
vblog(LOG_INFO, "Twitch chat connection opened");
Expand Down Expand Up @@ -615,6 +619,7 @@ void TwitchChatConnection::OnMessage(connection_hdl,
constexpr std::string_view authOKCommand = "001";
constexpr std::string_view pingCommand = "PING";
constexpr std::string_view joinOKCommand = "JOIN";
constexpr std::string_view noticeCommand = "NOTICE";
constexpr std::string_view newMessageCommand = "PRIVMSG";

if (!message) {
Expand All @@ -639,6 +644,8 @@ void TwitchChatConnection::OnMessage(connection_hdl,
HandleJoin(message);
} else if (message.command.command == newMessageCommand) {
HandleNewMessage(message);
} else if (message.command.command == noticeCommand) {
HandleNotice(message);
}
}
}
Expand Down Expand Up @@ -674,4 +681,9 @@ void TwitchChatConnection::Send(const std::string &msg)
}
}

bool TwitchChatConnection::ChatMapKey::operator<(const ChatMapKey &other) const
{
return channelName < other.channelName && token < other.token;
}

} // namespace advss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "channel-selection.hpp"
#include "token.hpp"

#include <websocketpp/client.hpp>
#include <QObject>
Expand All @@ -12,23 +13,50 @@
namespace advss {

using websocketpp::connection_hdl;
class TwitchToken;
struct IRCMessage;

struct ParsedTags {
using BadgeMap = std::unordered_map<std::string, std::string>;
using EmoteMap = std::unordered_map<std::string,
std::vector<std::pair<int, int>>>;
using EmoteSet = std::vector<std::string>;
std::unordered_map<std::string, std::variant<std::string, BadgeMap,
EmoteMap, EmoteSet>>
tagMap;
};

struct IRCMessage {
ParsedTags tags;
struct {
std::string nick;
std::string host;
} source;
struct {
std::string command;
std::variant<std::string, bool, std::vector<std::string>>
parameters;
} command;
std::string message;
};

class TwitchChatConnection : public QObject {
public:
explicit TwitchChatConnection();
virtual ~TwitchChatConnection();
~TwitchChatConnection();

void SetToken(const std::weak_ptr<TwitchToken> &);
void Connect();
void Disconnect();
std::vector<std::string> Messages(const TwitchChannel &channel);
static std::shared_ptr<TwitchChatConnection>
GetChatConnection(const TwitchToken &token,
const TwitchChannel &channel);
std::vector<IRCMessage> Messages();

static void ClearAllMessages();
void ClearMessages();

private:
TwitchChatConnection(const TwitchToken &token,
const TwitchChannel &channel);

void Connect();
void Disconnect();

void OnOpen(connection_hdl hdl);
void
OnMessage(connection_hdl hdl,
Expand All @@ -43,11 +71,21 @@ class TwitchChatConnection : public QObject {
void JoinChannel(const std::string &);
void HandleJoin(const IRCMessage &);
void HandleNewMessage(const IRCMessage &);
void HandleNotice(const IRCMessage &) const;

void RegisterInstance();
void UnregisterInstance();

std::weak_ptr<TwitchToken> _token;
struct ChatMapKey {
std::string channelName;
std::string token;
bool operator<(const ChatMapKey &) const;
};
static std::map<ChatMapKey, std::weak_ptr<TwitchChatConnection>>
_chatMap;

TwitchToken _token;
TwitchChannel _channel;
std::string _joinedChannelName;

websocketpp::client<websocketpp::config::asio_tls_client> _client;
Expand All @@ -62,7 +100,7 @@ class TwitchChatConnection : public QObject {
std::string _url;

std::mutex _messageMtx;
std::vector<std::string> _messages;
std::vector<IRCMessage> _messages;
static std::mutex _instancesMtx;
static std::vector<TwitchChatConnection *> _instances;
static bool _setupDone;
Expand Down
Loading

0 comments on commit 8af5686

Please sign in to comment.