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
+60 -57
View File
@@ -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;