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:
@@ -22,6 +22,8 @@ PubSubClient::PubSubClient(WebsocketClient &websocketClient,
|
||||
const PubSubClientOptions &clientOptions)
|
||||
: websocketClient_(websocketClient)
|
||||
, handle_(handle)
|
||||
, heartbeatTimer_(std::make_shared<boost::asio::steady_timer>(
|
||||
this->websocketClient_.get_io_service()))
|
||||
, clientOptions_(clientOptions)
|
||||
{
|
||||
}
|
||||
@@ -40,6 +42,7 @@ void PubSubClient::stop()
|
||||
assert(this->started_);
|
||||
|
||||
this->started_ = false;
|
||||
this->heartbeatTimer_->cancel();
|
||||
}
|
||||
|
||||
void PubSubClient::close(const std::string &reason,
|
||||
@@ -187,8 +190,8 @@ void PubSubClient::ping()
|
||||
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
runAfter(this->websocketClient_.get_io_service(),
|
||||
this->clientOptions_.pingInterval_, [self](auto timer) {
|
||||
runAfter(this->heartbeatTimer_, this->clientOptions_.pingInterval_,
|
||||
[self](auto timer) {
|
||||
if (!self->started_)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -70,6 +70,7 @@ private:
|
||||
std::atomic<bool> awaitingPong_{false};
|
||||
std::atomic<bool> started_{false};
|
||||
|
||||
std::shared_ptr<boost::asio::steady_timer> heartbeatTimer_;
|
||||
const PubSubClientOptions &clientOptions_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,6 +41,13 @@ struct PubSubListenMessage;
|
||||
struct PubSubMessage;
|
||||
struct PubSubMessageMessage;
|
||||
|
||||
/**
|
||||
* This handles the Twitch PubSub connection
|
||||
*
|
||||
* Known issues:
|
||||
* - Upon closing a channel, we don't unsubscribe to its pubsub connections
|
||||
* - Stop is never called, meaning we never do a clean shutdown
|
||||
*/
|
||||
class PubSub
|
||||
{
|
||||
using WebsocketMessagePtr =
|
||||
@@ -60,75 +67,62 @@ class PubSub
|
||||
};
|
||||
|
||||
WebsocketClient websocketClient;
|
||||
std::unique_ptr<std::thread> mainThread;
|
||||
std::unique_ptr<std::thread> thread;
|
||||
|
||||
// Account credentials
|
||||
// Set from setAccount or setAccountData
|
||||
// Set from setAccount
|
||||
QString token_;
|
||||
QString userID_;
|
||||
|
||||
public:
|
||||
PubSub(const QString &host,
|
||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
||||
~PubSub();
|
||||
|
||||
PubSub(const PubSub &) = delete;
|
||||
PubSub(PubSub &&) = delete;
|
||||
PubSub &operator=(const PubSub &) = delete;
|
||||
PubSub &operator=(PubSub &&) = delete;
|
||||
|
||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void setAccountData(QString token, QString userID);
|
||||
|
||||
enum class State {
|
||||
Connected,
|
||||
Disconnected,
|
||||
};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool isConnected() const
|
||||
{
|
||||
return this->state == State::Connected;
|
||||
}
|
||||
struct {
|
||||
Signal<ClearChatAction> chatCleared;
|
||||
Signal<DeleteAction> messageDeleted;
|
||||
Signal<ModeChangedAction> modeChanged;
|
||||
Signal<ModerationStateAction> moderationStateChanged;
|
||||
|
||||
Signal<BanAction> userBanned;
|
||||
Signal<UnbanAction> userUnbanned;
|
||||
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousMessageReceived;
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousTreatmentUpdated;
|
||||
|
||||
// Message caught by automod
|
||||
// channelID
|
||||
pajlada::Signals::Signal<PubSubAutoModQueueMessage, QString>
|
||||
autoModMessageCaught;
|
||||
|
||||
// Message blocked by moderator
|
||||
Signal<AutomodAction> autoModMessageBlocked;
|
||||
|
||||
Signal<AutomodUserAction> automodUserMessage;
|
||||
Signal<AutomodInfoAction> automodInfoMessage;
|
||||
} moderation;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
Signal<ClearChatAction> chatCleared;
|
||||
Signal<DeleteAction> messageDeleted;
|
||||
Signal<ModeChangedAction> modeChanged;
|
||||
Signal<ModerationStateAction> moderationStateChanged;
|
||||
// Parsing should be done in PubSubManager as well,
|
||||
// but for now we just send the raw data
|
||||
Signal<const PubSubWhisperMessage &> received;
|
||||
Signal<const PubSubWhisperMessage &> sent;
|
||||
} whisper;
|
||||
|
||||
Signal<BanAction> userBanned;
|
||||
Signal<UnbanAction> userUnbanned;
|
||||
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousMessageReceived;
|
||||
Signal<PubSubLowTrustUsersMessage> suspiciousTreatmentUpdated;
|
||||
|
||||
// Message caught by automod
|
||||
// channelID
|
||||
pajlada::Signals::Signal<PubSubAutoModQueueMessage, QString>
|
||||
autoModMessageCaught;
|
||||
|
||||
// Message blocked by moderator
|
||||
Signal<AutomodAction> autoModMessageBlocked;
|
||||
|
||||
Signal<AutomodUserAction> automodUserMessage;
|
||||
Signal<AutomodInfoAction> automodInfoMessage;
|
||||
} moderation;
|
||||
|
||||
struct {
|
||||
// Parsing should be done in PubSubManager as well,
|
||||
// but for now we just send the raw data
|
||||
Signal<const PubSubWhisperMessage &> received;
|
||||
Signal<const PubSubWhisperMessage &> sent;
|
||||
} whisper;
|
||||
|
||||
struct {
|
||||
Signal<const QJsonObject &> redeemed;
|
||||
} pointReward;
|
||||
} signals_;
|
||||
|
||||
void unlistenAllModerationActions();
|
||||
void unlistenAutomod();
|
||||
void unlistenLowTrustUsers();
|
||||
void unlistenWhispers();
|
||||
struct {
|
||||
Signal<const QJsonObject &> redeemed;
|
||||
} pointReward;
|
||||
|
||||
/**
|
||||
* Listen to incoming whispers for the currently logged in user.
|
||||
@@ -137,6 +131,7 @@ public:
|
||||
* PubSub topic: whispers.{currentUserID}
|
||||
*/
|
||||
bool listenToWhispers();
|
||||
void unlistenWhispers();
|
||||
|
||||
/**
|
||||
* Listen to moderation actions in the given channel.
|
||||
@@ -150,6 +145,7 @@ public:
|
||||
* PubSub topic: chat_moderator_actions.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToChannelModerationActions(const QString &channelID);
|
||||
void unlistenChannelModerationActions();
|
||||
|
||||
/**
|
||||
* Listen to Automod events in the given channel.
|
||||
@@ -160,6 +156,7 @@ public:
|
||||
* PubSub topic: automod-queue.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToAutomod(const QString &channelID);
|
||||
void unlistenAutomod();
|
||||
|
||||
/**
|
||||
* Listen to Low Trust events in the given channel.
|
||||
@@ -170,6 +167,7 @@ public:
|
||||
* PubSub topic: low-trust-users.{currentUserID}.{channelID}
|
||||
*/
|
||||
void listenToLowTrustUsers(const QString &channelID);
|
||||
void unlistenLowTrustUsers();
|
||||
|
||||
/**
|
||||
* Listen to incoming channel point redemptions in the given channel.
|
||||
@@ -178,8 +176,7 @@ public:
|
||||
* PubSub topic: community-points-channel-v1.{channelID}
|
||||
*/
|
||||
void listenToChannelPointRewards(const QString &channelID);
|
||||
|
||||
std::vector<QString> requests;
|
||||
void unlistenChannelPointRewards();
|
||||
|
||||
struct {
|
||||
std::atomic<uint32_t> connectionsClosed{0};
|
||||
@@ -192,20 +189,26 @@ public:
|
||||
std::atomic<uint32_t> unlistenResponses{0};
|
||||
} diag;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Unlistens to all topics matching the prefix in all clients
|
||||
*/
|
||||
void unlistenPrefix(const QString &prefix);
|
||||
|
||||
void listenToTopic(const QString &topic);
|
||||
|
||||
private:
|
||||
void listen(PubSubListenMessage msg);
|
||||
bool tryListen(PubSubListenMessage msg);
|
||||
|
||||
bool isListeningToTopic(const QString &topic);
|
||||
|
||||
void addClient();
|
||||
|
||||
std::vector<QString> requests;
|
||||
|
||||
std::atomic<bool> addingClient{false};
|
||||
ExponentialBackoff<5> connectBackoff{std::chrono::milliseconds(1000)};
|
||||
|
||||
State state = State::Connected;
|
||||
|
||||
std::map<WebsocketHandle, std::shared_ptr<PubSubClient>,
|
||||
std::owner_less<WebsocketHandle>>
|
||||
clients;
|
||||
|
||||
@@ -1250,15 +1250,15 @@ void TwitchChannel::refreshPubSub()
|
||||
|
||||
auto currentAccount = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
getApp()->twitch->pubsub->setAccount(currentAccount);
|
||||
getIApp()->getTwitchPubSub()->setAccount(currentAccount);
|
||||
|
||||
getApp()->twitch->pubsub->listenToChannelModerationActions(roomId);
|
||||
getIApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
||||
if (this->hasModRights())
|
||||
{
|
||||
getApp()->twitch->pubsub->listenToAutomod(roomId);
|
||||
getApp()->twitch->pubsub->listenToLowTrustUsers(roomId);
|
||||
getIApp()->getTwitchPubSub()->listenToAutomod(roomId);
|
||||
getIApp()->getTwitchPubSub()->listenToLowTrustUsers(roomId);
|
||||
}
|
||||
getApp()->twitch->pubsub->listenToChannelPointRewards(roomId);
|
||||
getIApp()->getTwitchPubSub()->listenToChannelPointRewards(roomId);
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshChatters()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "TwitchIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "providers/twitch/ChannelPointReward.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
@@ -29,7 +28,6 @@ using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
const QString TWITCH_PUBSUB_URL = "wss://pubsub-edge.twitch.tv";
|
||||
const QString BTTV_LIVE_UPDATES_URL = "wss://sockets.betterttv.net/ws";
|
||||
const QString SEVENTV_EVENTAPI_URL = "wss://events.7tv.io/v3";
|
||||
|
||||
@@ -43,7 +41,6 @@ TwitchIrcServer::TwitchIrcServer()
|
||||
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
|
||||
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||
, pubsub(new PubSub(TWITCH_PUBSUB_URL))
|
||||
{
|
||||
this->initializeIrc();
|
||||
|
||||
@@ -72,7 +69,6 @@ void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
|
||||
getApp()->accounts->twitch.currentUserChanged.connect([this]() {
|
||||
postToThread([this] {
|
||||
this->connect();
|
||||
this->pubsub->setAccount(getApp()->accounts->twitch.getCurrent());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace chatterino {
|
||||
|
||||
class Settings;
|
||||
class Paths;
|
||||
class PubSub;
|
||||
class TwitchChannel;
|
||||
class BttvLiveUpdates;
|
||||
class SeventvEventAPI;
|
||||
@@ -80,8 +79,6 @@ public:
|
||||
const ChannelPtr automodChannel;
|
||||
IndirectChannel watchingChannel;
|
||||
|
||||
// NOTE: We currently leak this
|
||||
PubSub *pubsub;
|
||||
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
|
||||
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user