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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user