refactor: Twitch PubSub client (#5059)

* Remove unused `setAccountData` function

* Move PubSub out of TwitchIrcServer and into Application

* Add changelog entry

* fix: assert feedback

* Add PubSub::unlistenPrefix as per review suggestion

* Fix tests

* quit pubsub on exit

might conflict with exit removal, so can be reverted but this shows it's possible

* Don't manually call stop on clients, it's called when the connection is closed

* nit: rename `mainThread` to `thread`

* Join in a thread!!!!!!!!
This commit is contained in:
pajlada
2024-01-06 13:18:37 +01:00
committed by GitHub
parent e48d868e8c
commit 416806bb0a
14 changed files with 458 additions and 484 deletions
+103 -133
View File
@@ -17,12 +17,14 @@
#include <algorithm>
#include <exception>
#include <future>
#include <iostream>
#include <thread>
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using namespace std::chrono_literals;
namespace chatterino {
@@ -36,7 +38,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
const auto &roomID) {
ClearChatAction action(data, roomID);
this->signals_.moderation.chatCleared.invoke(action);
this->moderation.chatCleared.invoke(action);
};
this->moderationActionHandlers["slowoff"] = [this](const auto &data,
@@ -46,7 +48,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::Slow;
action.state = ModeChangedAction::State::Off;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["slow"] = [this](const auto &data,
@@ -69,7 +71,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.duration = args.at(0).toString().toUInt(&ok, 10);
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data,
@@ -79,7 +81,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::R9K;
action.state = ModeChangedAction::State::Off;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["r9kbeta"] = [this](const auto &data,
@@ -89,7 +91,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::R9K;
action.state = ModeChangedAction::State::On;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["subscribersoff"] =
@@ -99,7 +101,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::SubscribersOnly;
action.state = ModeChangedAction::State::Off;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["subscribers"] = [this](const auto &data,
@@ -109,7 +111,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::SubscribersOnly;
action.state = ModeChangedAction::State::On;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["emoteonlyoff"] =
@@ -119,7 +121,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::EmoteOnly;
action.state = ModeChangedAction::State::Off;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["emoteonly"] = [this](const auto &data,
@@ -129,7 +131,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.mode = ModeChangedAction::Mode::EmoteOnly;
action.state = ModeChangedAction::State::On;
this->signals_.moderation.modeChanged.invoke(action);
this->moderation.modeChanged.invoke(action);
};
this->moderationActionHandlers["unmod"] = [this](const auto &data,
@@ -149,7 +151,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.modded = false;
this->signals_.moderation.moderationStateChanged.invoke(action);
this->moderation.moderationStateChanged.invoke(action);
};
this->moderationActionHandlers["mod"] = [this](const auto &data,
@@ -167,7 +169,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.target.id = data.value("target_user_id").toString();
action.target.login = data.value("target_user_login").toString();
this->signals_.moderation.moderationStateChanged.invoke(action);
this->moderation.moderationStateChanged.invoke(action);
};
this->moderationActionHandlers["timeout"] = [this](const auto &data,
@@ -191,7 +193,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.duration = args[1].toString().toUInt(&ok, 10);
action.reason = args[2].toString(); // May be omitted
this->signals_.moderation.userBanned.invoke(action);
this->moderation.userBanned.invoke(action);
};
this->moderationActionHandlers["delete"] = [this](const auto &data,
@@ -214,7 +216,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.messageText = args[1].toString();
action.messageId = args[2].toString();
this->signals_.moderation.messageDeleted.invoke(action);
this->moderation.messageDeleted.invoke(action);
};
this->moderationActionHandlers["ban"] = [this](const auto &data,
@@ -236,7 +238,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.target.login = args[0].toString();
action.reason = args[1].toString(); // May be omitted
this->signals_.moderation.userBanned.invoke(action);
this->moderation.userBanned.invoke(action);
};
this->moderationActionHandlers["unban"] = [this](const auto &data,
@@ -259,7 +261,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.target.login = args[0].toString();
this->signals_.moderation.userUnbanned.invoke(action);
this->moderation.userUnbanned.invoke(action);
};
this->moderationActionHandlers["untimeout"] = [this](const auto &data,
@@ -282,7 +284,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.target.login = args[0].toString();
this->signals_.moderation.userUnbanned.invoke(action);
this->moderation.userUnbanned.invoke(action);
};
/*
@@ -315,7 +317,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = args[1].toString(); // May be omitted
action.reason = args[2].toString(); // May be omitted
this->signals_.moderation.autoModMessageBlocked.invoke(action);
this->moderation.autoModMessageBlocked.invoke(action);
};
*/
@@ -323,21 +325,21 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
[this](const auto &data, const auto &roomID) {
AutomodInfoAction action(data, roomID);
action.type = AutomodInfoAction::OnHold;
this->signals_.moderation.automodInfoMessage.invoke(action);
this->moderation.automodInfoMessage.invoke(action);
};
this->moderationActionHandlers["automod_message_denied"] =
[this](const auto &data, const auto &roomID) {
AutomodInfoAction action(data, roomID);
action.type = AutomodInfoAction::Denied;
this->signals_.moderation.automodInfoMessage.invoke(action);
this->moderation.automodInfoMessage.invoke(action);
};
this->moderationActionHandlers["automod_message_approved"] =
[this](const auto &data, const auto &roomID) {
AutomodInfoAction action(data, roomID);
action.type = AutomodInfoAction::Approved;
this->signals_.moderation.automodInfoMessage.invoke(action);
this->moderation.automodInfoMessage.invoke(action);
};
this->channelTermsActionHandlers["add_permitted_term"] =
@@ -351,7 +353,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = data.value("text").toString();
action.source.login = data.value("requester_login").toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
this->channelTermsActionHandlers["add_blocked_term"] =
@@ -365,7 +367,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = data.value("text").toString();
action.source.login = data.value("requester_login").toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
this->moderationActionHandlers["delete_permitted_term"] =
@@ -385,7 +387,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = args[0].toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
this->channelTermsActionHandlers["delete_permitted_term"] =
@@ -399,7 +401,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = data.value("text").toString();
action.source.login = data.value("requester_login").toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
this->moderationActionHandlers["delete_blocked_term"] =
@@ -420,7 +422,7 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = args[0].toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
this->channelTermsActionHandlers["delete_blocked_term"] =
[this](const auto &data, const auto &roomID) {
@@ -434,21 +436,9 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
action.message = data.value("text").toString();
action.source.login = data.value("requester_login").toString();
this->signals_.moderation.automodUserMessage.invoke(action);
this->moderation.automodUserMessage.invoke(action);
};
// We don't get this one anymore or anything similiar
// We need some new topic so we can listen
//
//this->moderationActionHandlers["modified_automod_properties"] =
// [this](const auto &data, const auto &roomID) {
// // The automod settings got modified
// AutomodUserAction action(data, roomID);
// getCreatedByUser(data, action.source);
// action.type = AutomodUserAction::Properties;
// this->signals_.moderation.automodUserMessage.invoke(action);
// };
this->moderationActionHandlers["denied_automod_message"] =
[](const auto &data, const auto &roomID) {
// This message got denied by a moderator
@@ -482,18 +472,17 @@ PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
bind(&PubSub::onConnectionFail, this, ::_1));
}
PubSub::~PubSub()
{
this->stop();
}
void PubSub::setAccount(std::shared_ptr<TwitchAccount> account)
{
this->token_ = account->getOAuthToken();
this->userID_ = account->getUserId();
}
void PubSub::setAccountData(QString token, QString userID)
{
this->token_ = token;
this->userID_ = userID;
}
void PubSub::addClient()
{
if (this->addingClient)
@@ -525,104 +514,43 @@ void PubSub::start()
{
this->work = std::make_shared<boost::asio::io_service::work>(
this->websocketClient.get_io_service());
this->mainThread.reset(
new std::thread(std::bind(&PubSub::runThread, this)));
this->thread.reset(new std::thread(std::bind(&PubSub::runThread, this)));
}
void PubSub::stop()
{
this->stopping_ = true;
for (const auto &client : this->clients)
for (const auto &[hdl, client] : this->clients)
{
client.second->close("Shutting down");
(void)hdl;
client->close("Shutting down");
}
this->work.reset();
if (this->mainThread->joinable())
if (this->thread->joinable())
{
this->mainThread->join();
// NOTE: We spawn a new thread to join the websocket thread.
// There is a case where a new client was initiated but not added to the clients list.
// We just don't join the thread & let the operating system nuke the thread if joining fails
// within 1s.
// We could fix the underlying bug, but this is easier & we realistically won't use this exact code
// for super much longer.
auto joiner = std::async(std::launch::async, &std::thread::join,
this->thread.get());
if (joiner.wait_for(1s) == std::future_status::timeout)
{
qCWarning(chatterinoPubSub)
<< "Thread didn't join within 1 second, rip it out";
this->websocketClient.stop();
}
}
assert(this->clients.empty());
}
void PubSub::unlistenAllModerationActions()
{
for (const auto &p : this->clients)
{
const auto &client = p.second;
if (const auto &[topics, nonce] =
client->unlistenPrefix("chat_moderator_actions.");
!topics.empty())
{
this->registerNonce(nonce, {
client,
"UNLISTEN",
topics,
topics.size(),
});
}
}
}
void PubSub::unlistenAutomod()
{
for (const auto &p : this->clients)
{
const auto &client = p.second;
if (const auto &[topics, nonce] =
client->unlistenPrefix("automod-queue.");
!topics.empty())
{
this->registerNonce(nonce, {
client,
"UNLISTEN",
topics,
topics.size(),
});
}
}
}
void PubSub::unlistenLowTrustUsers()
{
for (const auto &p : this->clients)
{
const auto &client = p.second;
if (const auto &[topics, nonce] =
client->unlistenPrefix("low-trust-users.");
!topics.empty())
{
this->registerNonce(nonce, {
client,
"UNLISTEN",
topics,
topics.size(),
});
}
}
}
void PubSub::unlistenWhispers()
{
for (const auto &p : this->clients)
{
const auto &client = p.second;
if (const auto &[topics, nonce] = client->unlistenPrefix("whispers.");
!topics.empty())
{
this->registerNonce(nonce, {
client,
"UNLISTEN",
topics,
topics.size(),
});
}
}
}
bool PubSub::listenToWhispers()
{
if (this->userID_.isEmpty())
@@ -642,6 +570,11 @@ bool PubSub::listenToWhispers()
return true;
}
void PubSub::unlistenWhispers()
{
this->unlistenPrefix("whispers.");
}
void PubSub::listenToChannelModerationActions(const QString &channelID)
{
if (this->userID_.isEmpty())
@@ -666,6 +599,11 @@ void PubSub::listenToChannelModerationActions(const QString &channelID)
this->listenToTopic(topic);
}
void PubSub::unlistenChannelModerationActions()
{
this->unlistenPrefix("chat_moderator_actions.");
}
void PubSub::listenToAutomod(const QString &channelID)
{
if (this->userID_.isEmpty())
@@ -690,6 +628,11 @@ void PubSub::listenToAutomod(const QString &channelID)
this->listenToTopic(topic);
}
void PubSub::unlistenAutomod()
{
this->unlistenPrefix("automod-queue.");
}
void PubSub::listenToLowTrustUsers(const QString &channelID)
{
if (this->userID_.isEmpty())
@@ -714,6 +657,11 @@ void PubSub::listenToLowTrustUsers(const QString &channelID)
this->listenToTopic(topic);
}
void PubSub::unlistenLowTrustUsers()
{
this->unlistenPrefix("low-trust-users.");
}
void PubSub::listenToChannelPointRewards(const QString &channelID)
{
static const QString topicFormat("community-points-channel-v1.%1");
@@ -730,6 +678,30 @@ void PubSub::listenToChannelPointRewards(const QString &channelID)
this->listenToTopic(topic);
}
void PubSub::unlistenChannelPointRewards()
{
this->unlistenPrefix("community-points-channel-v1.");
}
void PubSub::unlistenPrefix(const QString &prefix)
{
for (const auto &p : this->clients)
{
const auto &client = p.second;
if (const auto &[topics, nonce] = client->unlistenPrefix(prefix);
!topics.empty())
{
NonceInfo nonceInfo{
client,
"UNLISTEN",
topics,
topics.size(),
};
this->registerNonce(nonce, nonceInfo);
}
}
}
void PubSub::listen(PubSubListenMessage msg)
{
if (this->tryListen(msg))
@@ -1083,11 +1055,11 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
switch (whisperMessage.type)
{
case PubSubWhisperMessage::Type::WhisperReceived: {
this->signals_.whisper.received.invoke(whisperMessage);
this->whisper.received.invoke(whisperMessage);
}
break;
case PubSubWhisperMessage::Type::WhisperSent: {
this->signals_.whisper.sent.invoke(whisperMessage);
this->whisper.sent.invoke(whisperMessage);
}
break;
case PubSubWhisperMessage::Type::Thread: {
@@ -1182,7 +1154,7 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
auto redemption =
innerMessage.data.value("redemption").toObject();
this->signals_.pointReward.redeemed.invoke(redemption);
this->pointReward.redeemed.invoke(redemption);
}
break;
@@ -1210,8 +1182,7 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
// Channel ID where the moderator actions are coming from
auto channelID = topicParts[2];
this->signals_.moderation.autoModMessageCaught.invoke(innerMessage,
channelID);
this->moderation.autoModMessageCaught.invoke(innerMessage, channelID);
}
else if (topic.startsWith("low-trust-users."))
{
@@ -1226,13 +1197,12 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
switch (innerMessage.type)
{
case PubSubLowTrustUsersMessage::Type::UserMessage: {
this->signals_.moderation.suspiciousMessageReceived.invoke(
innerMessage);
this->moderation.suspiciousMessageReceived.invoke(innerMessage);
}
break;
case PubSubLowTrustUsersMessage::Type::TreatmentUpdate: {
this->signals_.moderation.suspiciousTreatmentUpdated.invoke(
this->moderation.suspiciousTreatmentUpdated.invoke(
innerMessage);
}
break;