Improve Twitch PubSub connection reliability (#3643)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user