Improve Twitch PubSub connection reliability (#3643)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
nerix
2022-05-07 17:22:39 +02:00
committed by GitHub
parent 4aa5b04e37
commit f97780d84e
64 changed files with 3094 additions and 2126 deletions
@@ -0,0 +1,40 @@
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
namespace chatterino {
PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
: typeString(root.value("type").toString())
, data(root.value("data").toObject())
, status(this->data.value("status").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (oType.has_value())
{
this->type = oType.value();
}
auto contentClassification =
data.value("content_classification").toObject();
this->contentCategory = contentClassification.value("category").toString();
this->contentLevel = contentClassification.value("level").toInt();
auto message = data.value("message").toObject();
this->messageID = message.value("id").toString();
auto messageContent = message.value("content").toObject();
this->messageText = messageContent.value("text").toString();
auto messageSender = message.value("sender").toObject();
this->senderUserID = messageSender.value("user_id").toString();
this->senderUserLogin = messageSender.value("login").toString();
this->senderUserDisplayName =
messageSender.value("display_name").toString();
this->senderUserChatColor =
QColor(messageSender.value("chat_color").toString());
}
} // namespace chatterino
@@ -0,0 +1,53 @@
#pragma once
#include <QColor>
#include <QJsonObject>
#include <QString>
#include <magic_enum.hpp>
namespace chatterino {
struct PubSubAutoModQueueMessage {
enum class Type {
AutoModCaughtMessage,
INVALID,
};
QString typeString;
Type type = Type::INVALID;
QJsonObject data;
QString status;
QString contentCategory;
int contentLevel;
QString messageID;
QString messageText;
QString senderUserID;
QString senderUserLogin;
QString senderUserDisplayName;
QColor senderUserChatColor;
PubSubAutoModQueueMessage(const QJsonObject &root);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::PubSubAutoModQueueMessage::Type>(
chatterino::PubSubAutoModQueueMessage::Type value) noexcept
{
switch (value)
{
case chatterino::PubSubAutoModQueueMessage::Type::AutoModCaughtMessage:
return "automod_caught_message";
default:
return default_tag;
}
}
@@ -0,0 +1,19 @@
#include "providers/twitch/pubsubmessages/Base.hpp"
namespace chatterino {
PubSubMessage::PubSubMessage(QJsonObject _object)
: object(std::move(_object))
, nonce(this->object.value("nonce").toString())
, error(this->object.value("error").toString())
, typeString(this->object.value("type").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (oType.has_value())
{
this->type = oType.value();
}
}
} // namespace chatterino
@@ -0,0 +1,83 @@
#pragma once
#include <QJsonDocument>
#include <QJsonObject>
#include <QString>
#include <magic_enum.hpp>
#include <boost/optional.hpp>
namespace chatterino {
struct PubSubMessage {
enum class Type {
Pong,
Response,
Message,
INVALID,
};
QJsonObject object;
QString nonce;
QString error;
QString typeString;
Type type;
PubSubMessage(QJsonObject _object);
template <class InnerClass>
boost::optional<InnerClass> toInner();
};
template <class InnerClass>
boost::optional<InnerClass> PubSubMessage::toInner()
{
auto dataValue = this->object.value("data");
if (!dataValue.isObject())
{
return boost::none;
}
auto data = dataValue.toObject();
return InnerClass{this->nonce, data};
}
static boost::optional<PubSubMessage> parsePubSubBaseMessage(
const QString &blob)
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8()));
if (jsonDoc.isNull())
{
return boost::none;
}
return PubSubMessage(jsonDoc.object());
}
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t
magic_enum::customize::enum_name<chatterino::PubSubMessage::Type>(
chatterino::PubSubMessage::Type value) noexcept
{
switch (value)
{
case chatterino::PubSubMessage::Type::Pong:
return "PONG";
case chatterino::PubSubMessage::Type::Response:
return "RESPONSE";
case chatterino::PubSubMessage::Type::Message:
return "MESSAGE";
default:
return default_tag;
}
}
@@ -0,0 +1,17 @@
#include "providers/twitch/pubsubmessages/ChannelPoints.hpp"
namespace chatterino {
PubSubCommunityPointsChannelV1Message::PubSubCommunityPointsChannelV1Message(
const QJsonObject &root)
: typeString(root.value("type").toString())
, data(root.value("data").toObject())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (oType.has_value())
{
this->type = oType.value();
}
}
} // namespace chatterino
@@ -0,0 +1,40 @@
#pragma once
#include <QJsonObject>
#include <QString>
#include <magic_enum.hpp>
namespace chatterino {
struct PubSubCommunityPointsChannelV1Message {
enum class Type {
RewardRedeemed,
INVALID,
};
QString typeString;
Type type = Type::INVALID;
QJsonObject data;
PubSubCommunityPointsChannelV1Message(const QJsonObject &root);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::PubSubCommunityPointsChannelV1Message::Type>(
chatterino::PubSubCommunityPointsChannelV1Message::Type value) noexcept
{
switch (value)
{
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
RewardRedeemed:
return "reward-redeemed";
default:
return default_tag;
}
}
@@ -0,0 +1,17 @@
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp"
namespace chatterino {
PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage(
const QJsonObject &root)
: typeString(root.value("type").toString())
, data(root.value("data").toObject())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (oType.has_value())
{
this->type = oType.value();
}
}
} // namespace chatterino
@@ -0,0 +1,46 @@
#pragma once
#include <QJsonObject>
#include <QString>
#include <magic_enum.hpp>
namespace chatterino {
struct PubSubChatModeratorActionMessage {
enum class Type {
ModerationAction,
ChannelTermsAction,
INVALID,
};
QString typeString;
Type type = Type::INVALID;
QJsonObject data;
PubSubChatModeratorActionMessage(const QJsonObject &root);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::PubSubChatModeratorActionMessage::Type>(
chatterino::PubSubChatModeratorActionMessage::Type value) noexcept
{
switch (value)
{
case chatterino::PubSubChatModeratorActionMessage::Type::
ModerationAction:
return "moderation_action";
case chatterino::PubSubChatModeratorActionMessage::Type::
ChannelTermsAction:
return "channel_terms_action";
default:
return default_tag;
}
}
@@ -0,0 +1,50 @@
#include "providers/twitch/pubsubmessages/Listen.hpp"
#include "util/Helpers.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace chatterino {
PubSubListenMessage::PubSubListenMessage(std::vector<QString> _topics)
: topics(std::move(_topics))
, nonce(generateUuid())
{
}
void PubSubListenMessage::setToken(const QString &_token)
{
this->token = _token;
}
QByteArray PubSubListenMessage::toJson() const
{
QJsonObject root;
root["type"] = "LISTEN";
root["nonce"] = this->nonce;
{
QJsonObject data;
QJsonArray jsonTopics;
std::copy(this->topics.begin(), this->topics.end(),
std::back_inserter(jsonTopics));
data["topics"] = jsonTopics;
if (!this->token.isEmpty())
{
data["auth_token"] = this->token;
}
root["data"] = data;
}
return QJsonDocument(root).toJson();
}
} // namespace chatterino
@@ -0,0 +1,24 @@
#pragma once
#include <QString>
#include <vector>
namespace chatterino {
// PubSubListenMessage is an outgoing LISTEN message that is sent for the client to subscribe to a list of topics
struct PubSubListenMessage {
const std::vector<QString> topics;
const QString nonce;
QString token;
PubSubListenMessage(std::vector<QString> _topics);
void setToken(const QString &_token);
QByteArray toJson() const;
};
} // namespace chatterino
@@ -0,0 +1,60 @@
#pragma once
#include "common/QLogging.hpp"
#include <QJsonDocument>
#include <QJsonObject>
#include <QString>
#include <boost/optional.hpp>
namespace chatterino {
struct PubSubMessageMessage {
QString nonce;
QString topic;
QJsonObject messageObject;
PubSubMessageMessage(QString _nonce, const QJsonObject &data)
: nonce(std::move(_nonce))
, topic(data.value("topic").toString())
{
auto messagePayload = data.value("message").toString().toUtf8();
auto messageDoc = QJsonDocument::fromJson(messagePayload);
if (messageDoc.isNull())
{
qCWarning(chatterinoPubSub) << "PubSub message (type MESSAGE) "
"missing inner message payload";
return;
}
if (!messageDoc.isObject())
{
qCWarning(chatterinoPubSub)
<< "PubSub message (type MESSAGE) inner message payload is not "
"an object";
return;
}
this->messageObject = messageDoc.object();
}
template <class InnerClass>
boost::optional<InnerClass> toInner() const;
};
template <class InnerClass>
boost::optional<InnerClass> PubSubMessageMessage::toInner() const
{
if (this->messageObject.empty())
{
return boost::none;
}
return InnerClass{this->messageObject};
}
} // namespace chatterino
@@ -0,0 +1,40 @@
#include "providers/twitch/pubsubmessages/Unlisten.hpp"
#include "util/Helpers.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace chatterino {
PubSubUnlistenMessage::PubSubUnlistenMessage(std::vector<QString> _topics)
: topics(std::move(_topics))
, nonce(generateUuid())
{
}
QByteArray PubSubUnlistenMessage::toJson() const
{
QJsonObject root;
root["type"] = "UNLISTEN";
root["nonce"] = this->nonce;
{
QJsonObject data;
QJsonArray jsonTopics;
std::copy(this->topics.begin(), this->topics.end(),
std::back_inserter(jsonTopics));
data["topics"] = jsonTopics;
root["data"] = data;
}
return QJsonDocument(root).toJson();
}
} // namespace chatterino
@@ -0,0 +1,20 @@
#pragma once
#include <QString>
#include <vector>
namespace chatterino {
// PubSubUnlistenMessage is an outgoing UNLISTEN message that is sent for the client to unsubscribe from a list of topics
struct PubSubUnlistenMessage {
const std::vector<QString> topics;
const QString nonce;
PubSubUnlistenMessage(std::vector<QString> _topics);
QByteArray toJson() const;
};
} // namespace chatterino
@@ -0,0 +1,38 @@
#include "providers/twitch/pubsubmessages/Whisper.hpp"
namespace chatterino {
PubSubWhisperMessage::PubSubWhisperMessage(const QJsonObject &root)
: typeString(root.value("type").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (oType.has_value())
{
this->type = oType.value();
}
// Parse information from data_object
auto data = root.value("data_object").toObject();
this->messageID = data.value("message_id").toString();
this->id = data.value("id").toInt();
this->threadID = data.value("thread_id").toString();
this->body = data.value("body").toString();
auto fromID = data.value("from_id");
if (fromID.isString())
{
this->fromUserID = fromID.toString();
}
else
{
this->fromUserID = QString::number(data.value("from_id").toInt());
}
auto tags = data.value("tags").toObject();
this->fromUserLogin = tags.value("login").toString();
this->fromUserDisplayName = tags.value("display_name").toString();
this->fromUserColor = QColor(tags.value("color").toString());
}
} // namespace chatterino
@@ -0,0 +1,55 @@
#pragma once
#include <QColor>
#include <QJsonObject>
#include <QString>
#include <magic_enum.hpp>
namespace chatterino {
struct PubSubWhisperMessage {
enum class Type {
WhisperReceived,
WhisperSent,
Thread,
INVALID,
};
QString typeString;
Type type = Type::INVALID;
QString messageID;
int id;
QString threadID;
QString body;
QString fromUserID;
QString fromUserLogin;
QString fromUserDisplayName;
QColor fromUserColor;
PubSubWhisperMessage(const QJsonObject &root);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t
magic_enum::customize::enum_name<chatterino::PubSubWhisperMessage::Type>(
chatterino::PubSubWhisperMessage::Type value) noexcept
{
switch (value)
{
case chatterino::PubSubWhisperMessage::Type::WhisperReceived:
return "whisper_received";
case chatterino::PubSubWhisperMessage::Type::WhisperSent:
return "whisper_sent";
case chatterino::PubSubWhisperMessage::Type::Thread:
return "thread";
default:
return default_tag;
}
}