Refactor 7TV EventAPI (#4342)

* refactor: remove file prefix

* refactor: put eventapi into a namespace

* refactor: support multiple conditions

* fix: handle ack

* docs: add changelog entry

* fix: unsubscribe in destructor

* fix: naming of parameter

* fix: namespaces

* chore: casing

* fix: missing namespace

---------

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
nerix
2023-02-04 13:42:52 +01:00
committed by GitHub
parent 6516116244
commit 0c74ab17ed
23 changed files with 580 additions and 493 deletions
+1
View File
@@ -44,6 +44,7 @@
- Dev: Format YAML and JSON files with prettier. (#4304) - Dev: Format YAML and JSON files with prettier. (#4304)
- Dev: Added CMake Install Support on Windows. (#4300) - Dev: Added CMake Install Support on Windows. (#4300)
- Dev: Changed conan generator to [`CMakeDeps`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) and [`CMakeToolchain`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmaketoolchain.html). See PR for migration notes. (#4335) - Dev: Changed conan generator to [`CMakeDeps`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) and [`CMakeToolchain`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmaketoolchain.html). See PR for migration notes. (#4335)
- Dev: Refactored 7TV EventAPI implementation. (#4342)
## 2.4.0 ## 2.4.0
+2 -2
View File
@@ -19,8 +19,8 @@
#include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/ffz/FfzBadges.hpp" #include "providers/ffz/FfzBadges.hpp"
#include "providers/irc/Irc2.hpp" #include "providers/irc/Irc2.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp" #include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/seventv/SeventvBadges.hpp" #include "providers/seventv/SeventvBadges.hpp"
#include "providers/seventv/SeventvEventAPI.hpp" #include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/ChannelPointReward.hpp"
+8 -8
View File
@@ -250,14 +250,14 @@ set(SOURCE_FILES
providers/seventv/SeventvEventAPI.cpp providers/seventv/SeventvEventAPI.cpp
providers/seventv/SeventvEventAPI.hpp providers/seventv/SeventvEventAPI.hpp
providers/seventv/eventapi/SeventvEventAPIClient.cpp providers/seventv/eventapi/Client.cpp
providers/seventv/eventapi/SeventvEventAPIClient.hpp providers/seventv/eventapi/Client.hpp
providers/seventv/eventapi/SeventvEventAPIDispatch.cpp providers/seventv/eventapi/Dispatch.cpp
providers/seventv/eventapi/SeventvEventAPIDispatch.hpp providers/seventv/eventapi/Dispatch.hpp
providers/seventv/eventapi/SeventvEventAPIMessage.cpp providers/seventv/eventapi/Message.cpp
providers/seventv/eventapi/SeventvEventAPIMessage.hpp providers/seventv/eventapi/Message.hpp
providers/seventv/eventapi/SeventvEventAPISubscription.cpp providers/seventv/eventapi/Subscription.cpp
providers/seventv/eventapi/SeventvEventAPISubscription.hpp providers/seventv/eventapi/Subscription.hpp
providers/twitch/ChannelPointReward.cpp providers/twitch/ChannelPointReward.cpp
providers/twitch/ChannelPointReward.hpp providers/twitch/ChannelPointReward.hpp
+8 -5
View File
@@ -7,7 +7,7 @@
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include "messages/ImageSet.hpp" #include "messages/ImageSet.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
@@ -30,6 +30,7 @@
namespace { namespace {
using namespace chatterino; using namespace chatterino;
using namespace seventv::eventapi;
// These declarations won't throw an exception. // These declarations won't throw an exception.
const QString CHANNEL_HAS_NO_EMOTES("This channel has no 7TV channel emotes."); const QString CHANNEL_HAS_NO_EMOTES("This channel has no 7TV channel emotes.");
@@ -224,7 +225,7 @@ EmoteMap parseEmotes(const QJsonArray &emoteSetEmotes, bool isGlobal)
} }
EmotePtr createUpdatedEmote(const EmotePtr &oldEmote, EmotePtr createUpdatedEmote(const EmotePtr &oldEmote,
const SeventvEventAPIEmoteUpdateDispatch &dispatch) const EmoteUpdateDispatch &dispatch)
{ {
bool toNonAliased = oldEmote->baseName.has_value() && bool toNonAliased = oldEmote->baseName.has_value() &&
dispatch.emoteName == oldEmote->baseName->string; dispatch.emoteName == oldEmote->baseName->string;
@@ -245,6 +246,8 @@ EmotePtr createUpdatedEmote(const EmotePtr &oldEmote,
namespace chatterino { namespace chatterino {
using namespace seventv::eventapi;
SeventvEmotes::SeventvEmotes() SeventvEmotes::SeventvEmotes()
: global_(std::make_shared<EmoteMap>()) : global_(std::make_shared<EmoteMap>())
{ {
@@ -401,7 +404,7 @@ void SeventvEmotes::loadChannelEmotes(
boost::optional<EmotePtr> SeventvEmotes::addEmote( boost::optional<EmotePtr> SeventvEmotes::addEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteAddDispatch &dispatch) const EmoteAddDispatch &dispatch)
{ {
// Check for visibility first, so we don't copy the map. // Check for visibility first, so we don't copy the map.
auto emoteData = dispatch.emoteJson["data"].toObject(); auto emoteData = dispatch.emoteJson["data"].toObject();
@@ -429,7 +432,7 @@ boost::optional<EmotePtr> SeventvEmotes::addEmote(
boost::optional<EmotePtr> SeventvEmotes::updateEmote( boost::optional<EmotePtr> SeventvEmotes::updateEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteUpdateDispatch &dispatch) const EmoteUpdateDispatch &dispatch)
{ {
auto oldMap = map.get(); auto oldMap = map.get();
auto oldEmote = oldMap->findEmote(dispatch.emoteName, dispatch.emoteID); auto oldEmote = oldMap->findEmote(dispatch.emoteName, dispatch.emoteID);
@@ -451,7 +454,7 @@ boost::optional<EmotePtr> SeventvEmotes::updateEmote(
boost::optional<EmotePtr> SeventvEmotes::removeEmote( boost::optional<EmotePtr> SeventvEmotes::removeEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteRemoveDispatch &dispatch) const EmoteRemoveDispatch &dispatch)
{ {
// This copies the map. // This copies the map.
EmoteMap updatedMap = *map.get(); EmoteMap updatedMap = *map.get();
+9 -6
View File
@@ -10,9 +10,12 @@
namespace chatterino { namespace chatterino {
class Channel; class Channel;
struct SeventvEventAPIEmoteAddDispatch;
struct SeventvEventAPIEmoteUpdateDispatch; namespace seventv::eventapi {
struct SeventvEventAPIEmoteRemoveDispatch; struct EmoteAddDispatch;
struct EmoteUpdateDispatch;
struct EmoteRemoveDispatch;
} // namespace seventv::eventapi
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36 // https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
enum class SeventvActiveEmoteFlag : int64_t { enum class SeventvActiveEmoteFlag : int64_t {
@@ -86,7 +89,7 @@ public:
*/ */
static boost::optional<EmotePtr> addEmote( static boost::optional<EmotePtr> addEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteAddDispatch &dispatch); const seventv::eventapi::EmoteAddDispatch &dispatch);
/** /**
* Updates an emote in this `map`. * Updates an emote in this `map`.
@@ -97,7 +100,7 @@ public:
*/ */
static boost::optional<EmotePtr> updateEmote( static boost::optional<EmotePtr> updateEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteUpdateDispatch &dispatch); const seventv::eventapi::EmoteUpdateDispatch &dispatch);
/** /**
* Removes an emote from this `map`. * Removes an emote from this `map`.
@@ -108,7 +111,7 @@ public:
*/ */
static boost::optional<EmotePtr> removeEmote( static boost::optional<EmotePtr> removeEmote(
Atomic<std::shared_ptr<const EmoteMap>> &map, Atomic<std::shared_ptr<const EmoteMap>> &map,
const SeventvEventAPIEmoteRemoveDispatch &dispatch); const seventv::eventapi::EmoteRemoveDispatch &dispatch);
/** Fetches an emote-set by its id */ /** Fetches an emote-set by its id */
static void getEmoteSet( static void getEmoteSet(
+145 -132
View File
@@ -1,8 +1,8 @@
#include "providers/seventv/SeventvEventAPI.hpp" #include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp" #include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp" #include "providers/seventv/eventapi/Message.hpp"
#include <QJsonArray> #include <QJsonArray>
@@ -10,6 +10,8 @@
namespace chatterino { namespace chatterino {
using namespace seventv::eventapi;
SeventvEventAPI::SeventvEventAPI( SeventvEventAPI::SeventvEventAPI(
QString host, std::chrono::milliseconds defaultHeartbeatInterval) QString host, std::chrono::milliseconds defaultHeartbeatInterval)
: BasicPubSubManager(std::move(host)) : BasicPubSubManager(std::move(host))
@@ -22,13 +24,14 @@ void SeventvEventAPI::subscribeUser(const QString &userID,
{ {
if (!userID.isEmpty() && this->subscribedUsers_.insert(userID).second) if (!userID.isEmpty() && this->subscribedUsers_.insert(userID).second)
{ {
this->subscribe({userID, SeventvEventAPISubscriptionType::UpdateUser}); this->subscribe(
{ObjectIDCondition{userID}, SubscriptionType::UpdateUser});
} }
if (!emoteSetID.isEmpty() && if (!emoteSetID.isEmpty() &&
this->subscribedEmoteSets_.insert(emoteSetID).second) this->subscribedEmoteSets_.insert(emoteSetID).second)
{ {
this->subscribe( this->subscribe(
{emoteSetID, SeventvEventAPISubscriptionType::UpdateEmoteSet}); {ObjectIDCondition{emoteSetID}, SubscriptionType::UpdateEmoteSet});
} }
} }
@@ -37,7 +40,7 @@ void SeventvEventAPI::unsubscribeEmoteSet(const QString &id)
if (this->subscribedEmoteSets_.erase(id) > 0) if (this->subscribedEmoteSets_.erase(id) > 0)
{ {
this->unsubscribe( this->unsubscribe(
{id, SeventvEventAPISubscriptionType::UpdateEmoteSet}); {ObjectIDCondition{id}, SubscriptionType::UpdateEmoteSet});
} }
} }
@@ -45,27 +48,27 @@ void SeventvEventAPI::unsubscribeUser(const QString &id)
{ {
if (this->subscribedUsers_.erase(id) > 0) if (this->subscribedUsers_.erase(id) > 0)
{ {
this->unsubscribe({id, SeventvEventAPISubscriptionType::UpdateUser}); this->unsubscribe(
{ObjectIDCondition{id}, SubscriptionType::UpdateUser});
} }
} }
std::shared_ptr<BasicPubSubClient<SeventvEventAPISubscription>> std::shared_ptr<BasicPubSubClient<Subscription>> SeventvEventAPI::createClient(
SeventvEventAPI::createClient(liveupdates::WebsocketClient &client, liveupdates::WebsocketClient &client, websocketpp::connection_hdl hdl)
websocketpp::connection_hdl hdl)
{ {
auto shared = std::make_shared<SeventvEventAPIClient>( auto shared =
client, hdl, this->heartbeatInterval_); std::make_shared<Client>(client, hdl, this->heartbeatInterval_);
return std::static_pointer_cast< return std::static_pointer_cast<BasicPubSubClient<Subscription>>(
BasicPubSubClient<SeventvEventAPISubscription>>(std::move(shared)); std::move(shared));
} }
void SeventvEventAPI::onMessage( void SeventvEventAPI::onMessage(
websocketpp::connection_hdl hdl, websocketpp::connection_hdl hdl,
BasicPubSubManager<SeventvEventAPISubscription>::WebsocketMessagePtr msg) BasicPubSubManager<Subscription>::WebsocketMessagePtr msg)
{ {
const auto &payload = QString::fromStdString(msg->get_payload()); const auto &payload = QString::fromStdString(msg->get_payload());
auto pMessage = parseSeventvEventAPIBaseMessage(payload); auto pMessage = parseBaseMessage(payload);
if (!pMessage) if (!pMessage)
{ {
@@ -76,11 +79,10 @@ void SeventvEventAPI::onMessage(
auto message = *pMessage; auto message = *pMessage;
switch (message.op) switch (message.op)
{ {
case SeventvEventAPIOpcode::Hello: { case Opcode::Hello: {
if (auto client = this->findClient(hdl)) if (auto client = this->findClient(hdl))
{ {
if (auto *stvClient = if (auto *stvClient = dynamic_cast<Client *>(client.get()))
dynamic_cast<SeventvEventAPIClient *>(client.get()))
{ {
stvClient->setHeartbeatInterval( stvClient->setHeartbeatInterval(
message.data["heartbeat_interval"].toInt()); message.data["heartbeat_interval"].toInt());
@@ -88,19 +90,18 @@ void SeventvEventAPI::onMessage(
} }
} }
break; break;
case SeventvEventAPIOpcode::Heartbeat: { case Opcode::Heartbeat: {
if (auto client = this->findClient(hdl)) if (auto client = this->findClient(hdl))
{ {
if (auto *stvClient = if (auto *stvClient = dynamic_cast<Client *>(client.get()))
dynamic_cast<SeventvEventAPIClient *>(client.get()))
{ {
stvClient->handleHeartbeat(); stvClient->handleHeartbeat();
} }
} }
} }
break; break;
case SeventvEventAPIOpcode::Dispatch: { case Opcode::Dispatch: {
auto dispatch = message.toInner<SeventvEventAPIDispatch>(); auto dispatch = message.toInner<Dispatch>();
if (!dispatch) if (!dispatch)
{ {
qCDebug(chatterinoSeventvEventAPI) qCDebug(chatterinoSeventvEventAPI)
@@ -110,133 +111,37 @@ void SeventvEventAPI::onMessage(
this->handleDispatch(*dispatch); this->handleDispatch(*dispatch);
} }
break; break;
case SeventvEventAPIOpcode::Reconnect: { case Opcode::Reconnect: {
if (auto client = this->findClient(hdl)) if (auto client = this->findClient(hdl))
{ {
if (auto *stvClient = if (auto *stvClient = dynamic_cast<Client *>(client.get()))
dynamic_cast<SeventvEventAPIClient *>(client.get()))
{ {
stvClient->close("Reconnecting"); stvClient->close("Reconnecting");
} }
} }
} }
break; break;
case Opcode::Ack: {
// unhandled
}
break;
default: { default: {
qCDebug(chatterinoSeventvEventAPI) << "Unhandled op: " << payload; qCDebug(chatterinoSeventvEventAPI) << "Unhandled op:" << payload;
} }
break; break;
} }
} }
void SeventvEventAPI::handleDispatch(const SeventvEventAPIDispatch &dispatch) void SeventvEventAPI::handleDispatch(const Dispatch &dispatch)
{ {
switch (dispatch.type) switch (dispatch.type)
{ {
case SeventvEventAPISubscriptionType::UpdateEmoteSet: { case SubscriptionType::UpdateEmoteSet: {
// dispatchBody: { this->onEmoteSetUpdate(dispatch);
// pushed: Array<{ key, value }>,
// pulled: Array<{ key, old_value }>,
// updated: Array<{ key, value, old_value }>,
// }
for (const auto pushedRef : dispatch.body["pushed"].toArray())
{
auto pushed = pushedRef.toObject();
if (pushed["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteAddDispatch added(
dispatch, pushed["value"].toObject());
if (added.validate())
{
this->signals_.emoteAdded.invoke(added);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteUpdateDispatch update(
dispatch, updated["old_value"].toObject(),
updated["value"].toObject());
if (update.validate())
{
this->signals_.emoteUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto pulledRef : dispatch.body["pulled"].toArray())
{
auto pulled = pulledRef.toObject();
if (pulled["key"].toString() != "emotes")
{
continue;
}
SeventvEventAPIEmoteRemoveDispatch removed(
dispatch, pulled["old_value"].toObject());
if (removed.validate())
{
this->signals_.emoteRemoved.invoke(removed);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
} }
break; break;
case SeventvEventAPISubscriptionType::UpdateUser: { case SubscriptionType::UpdateUser: {
// dispatchBody: { this->onUserUpdate(dispatch);
// updated: Array<{ key, value: Array<{key, value}> }>
// }
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "connections")
{
continue;
}
for (const auto valueRef : updated["value"].toArray())
{
auto value = valueRef.toObject();
if (value["key"].toString() != "emote_set")
{
continue;
}
SeventvEventAPIUserConnectionUpdateDispatch update(
dispatch, value, (size_t)updated["index"].toInt());
if (update.validate())
{
this->signals_.userUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
} }
break; break;
default: { default: {
@@ -248,4 +153,112 @@ void SeventvEventAPI::handleDispatch(const SeventvEventAPIDispatch &dispatch)
} }
} }
void SeventvEventAPI::onEmoteSetUpdate(const Dispatch &dispatch)
{
// dispatchBody: {
// pushed: Array<{ key, value }>,
// pulled: Array<{ key, old_value }>,
// updated: Array<{ key, value, old_value }>,
// }
for (const auto pushedRef : dispatch.body["pushed"].toArray())
{
auto pushed = pushedRef.toObject();
if (pushed["key"].toString() != "emotes")
{
continue;
}
const EmoteAddDispatch added(dispatch, pushed["value"].toObject());
if (added.validate())
{
this->signals_.emoteAdded.invoke(added);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "emotes")
{
continue;
}
const EmoteUpdateDispatch update(dispatch,
updated["old_value"].toObject(),
updated["value"].toObject());
if (update.validate())
{
this->signals_.emoteUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
for (const auto pulledRef : dispatch.body["pulled"].toArray())
{
auto pulled = pulledRef.toObject();
if (pulled["key"].toString() != "emotes")
{
continue;
}
const EmoteRemoveDispatch removed(dispatch,
pulled["old_value"].toObject());
if (removed.validate())
{
this->signals_.emoteRemoved.invoke(removed);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
void SeventvEventAPI::onUserUpdate(const Dispatch &dispatch)
{
// dispatchBody: {
// updated: Array<{ key, value: Array<{key, value}> }>
// }
for (const auto updatedRef : dispatch.body["updated"].toArray())
{
auto updated = updatedRef.toObject();
if (updated["key"].toString() != "connections")
{
continue;
}
for (const auto valueRef : updated["value"].toArray())
{
auto value = valueRef.toObject();
if (value["key"].toString() != "emote_set")
{
continue;
}
const UserConnectionUpdateDispatch update(
dispatch, value, (size_t)updated["index"].toInt());
if (update.validate())
{
this->signals_.userUpdated.invoke(update);
}
else
{
qCDebug(chatterinoSeventvEventAPI)
<< "Invalid dispatch" << dispatch.body;
}
}
}
}
} // namespace chatterino } // namespace chatterino
+22 -14
View File
@@ -8,14 +8,17 @@
namespace chatterino { namespace chatterino {
struct SeventvEventAPISubscription; namespace seventv::eventapi {
struct SeventvEventAPIDispatch; struct Subscription;
struct SeventvEventAPIEmoteAddDispatch; struct Dispatch;
struct SeventvEventAPIEmoteUpdateDispatch; struct EmoteAddDispatch;
struct SeventvEventAPIEmoteRemoveDispatch; struct EmoteUpdateDispatch;
struct SeventvEventAPIUserConnectionUpdateDispatch; struct EmoteRemoveDispatch;
struct UserConnectionUpdateDispatch;
} // namespace seventv::eventapi
class SeventvEventAPI : public BasicPubSubManager<SeventvEventAPISubscription> class SeventvEventAPI
: public BasicPubSubManager<seventv::eventapi::Subscription>
{ {
template <typename T> template <typename T>
using Signal = using Signal =
@@ -27,10 +30,10 @@ public:
std::chrono::milliseconds(25000)); std::chrono::milliseconds(25000));
struct { struct {
Signal<SeventvEventAPIEmoteAddDispatch> emoteAdded; Signal<seventv::eventapi::EmoteAddDispatch> emoteAdded;
Signal<SeventvEventAPIEmoteUpdateDispatch> emoteUpdated; Signal<seventv::eventapi::EmoteUpdateDispatch> emoteUpdated;
Signal<SeventvEventAPIEmoteRemoveDispatch> emoteRemoved; Signal<seventv::eventapi::EmoteRemoveDispatch> emoteRemoved;
Signal<SeventvEventAPIUserConnectionUpdateDispatch> userUpdated; Signal<seventv::eventapi::UserConnectionUpdateDispatch> userUpdated;
} signals_; // NOLINT(readability-identifier-naming) } signals_; // NOLINT(readability-identifier-naming)
/** /**
@@ -48,18 +51,23 @@ public:
void unsubscribeEmoteSet(const QString &id); void unsubscribeEmoteSet(const QString &id);
protected: protected:
std::shared_ptr<BasicPubSubClient<SeventvEventAPISubscription>> std::shared_ptr<BasicPubSubClient<seventv::eventapi::Subscription>>
createClient(liveupdates::WebsocketClient &client, createClient(liveupdates::WebsocketClient &client,
websocketpp::connection_hdl hdl) override; websocketpp::connection_hdl hdl) override;
void onMessage( void onMessage(
websocketpp::connection_hdl hdl, websocketpp::connection_hdl hdl,
BasicPubSubManager<SeventvEventAPISubscription>::WebsocketMessagePtr BasicPubSubManager<seventv::eventapi::Subscription>::WebsocketMessagePtr
msg) override; msg) override;
private: private:
void handleDispatch(const SeventvEventAPIDispatch &dispatch); void handleDispatch(const seventv::eventapi::Dispatch &dispatch);
void onEmoteSetUpdate(const seventv::eventapi::Dispatch &dispatch);
void onUserUpdate(const seventv::eventapi::Dispatch &dispatch);
/** emote-set ids */
std::unordered_set<QString> subscribedEmoteSets_; std::unordered_set<QString> subscribedEmoteSets_;
/** user ids */
std::unordered_set<QString> subscribedUsers_; std::unordered_set<QString> subscribedUsers_;
std::chrono::milliseconds heartbeatInterval_; std::chrono::milliseconds heartbeatInterval_;
}; };
@@ -1,44 +1,42 @@
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp" #include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp" #include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/twitch/PubSubHelpers.hpp" #include "providers/twitch/PubSubHelpers.hpp"
#include <utility> #include <utility>
namespace chatterino { namespace chatterino::seventv::eventapi {
SeventvEventAPIClient::SeventvEventAPIClient( Client::Client(liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketClient &websocketClient, liveupdates::WebsocketHandle handle,
liveupdates::WebsocketHandle handle, std::chrono::milliseconds heartbeatInterval)
std::chrono::milliseconds heartbeatInterval) : BasicPubSubClient<Subscription>(websocketClient, std::move(handle))
: BasicPubSubClient<SeventvEventAPISubscription>(websocketClient,
std::move(handle))
, lastHeartbeat_(std::chrono::steady_clock::now()) , lastHeartbeat_(std::chrono::steady_clock::now())
, heartbeatInterval_(heartbeatInterval) , heartbeatInterval_(heartbeatInterval)
{ {
} }
void SeventvEventAPIClient::onConnectionEstablished() void Client::onConnectionEstablished()
{ {
this->lastHeartbeat_.store(std::chrono::steady_clock::now(), this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
std::memory_order_release); std::memory_order_release);
this->checkHeartbeat(); this->checkHeartbeat();
} }
void SeventvEventAPIClient::setHeartbeatInterval(int intervalMs) void Client::setHeartbeatInterval(int intervalMs)
{ {
qCDebug(chatterinoSeventvEventAPI) qCDebug(chatterinoSeventvEventAPI)
<< "Setting expected heartbeat interval to" << intervalMs << "ms"; << "Setting expected heartbeat interval to" << intervalMs << "ms";
this->heartbeatInterval_ = std::chrono::milliseconds(intervalMs); this->heartbeatInterval_ = std::chrono::milliseconds(intervalMs);
} }
void SeventvEventAPIClient::handleHeartbeat() void Client::handleHeartbeat()
{ {
this->lastHeartbeat_.store(std::chrono::steady_clock::now(), this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
std::memory_order_release); std::memory_order_release);
} }
void SeventvEventAPIClient::checkHeartbeat() void Client::checkHeartbeat()
{ {
// Following the heartbeat docs, a connection is dead // Following the heartbeat docs, a connection is dead
// after three missed heartbeats. // after three missed heartbeats.
@@ -54,8 +52,7 @@ void SeventvEventAPIClient::checkHeartbeat()
return; return;
} }
auto self = std::dynamic_pointer_cast<SeventvEventAPIClient>( auto self = std::dynamic_pointer_cast<Client>(this->shared_from_this());
this->shared_from_this());
runAfter(this->websocketClient_.get_io_service(), this->heartbeatInterval_, runAfter(this->websocketClient_.get_io_service(), this->heartbeatInterval_,
[self](auto) { [self](auto) {
@@ -67,4 +64,4 @@ void SeventvEventAPIClient::checkHeartbeat()
}); });
} }
} // namespace chatterino } // namespace chatterino::seventv::eventapi
@@ -2,18 +2,22 @@
#include "providers/liveupdates/BasicPubSubClient.hpp" #include "providers/liveupdates/BasicPubSubClient.hpp"
// this needs to be included for the specialization // this needs to be included for the specialization
// of std::hash for SeventvEventAPISubscription // of std::hash for Subscription
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp" #include "providers/seventv/eventapi/Subscription.hpp"
namespace chatterino { namespace chatterino {
class SeventvEventAPI;
class SeventvEventAPIClient } // namespace chatterino
: public BasicPubSubClient<SeventvEventAPISubscription>
namespace chatterino::seventv::eventapi {
class Client : public BasicPubSubClient<Subscription>
{ {
public: public:
SeventvEventAPIClient(liveupdates::WebsocketClient &websocketClient, Client(liveupdates::WebsocketClient &websocketClient,
liveupdates::WebsocketHandle handle, liveupdates::WebsocketHandle handle,
std::chrono::milliseconds heartbeatInterval); std::chrono::milliseconds heartbeatInterval);
void setHeartbeatInterval(int intervalMs); void setHeartbeatInterval(int intervalMs);
void handleHeartbeat(); void handleHeartbeat();
@@ -29,7 +33,7 @@ private:
// This will be set once on the welcome message. // This will be set once on the welcome message.
std::chrono::milliseconds heartbeatInterval_; std::chrono::milliseconds heartbeatInterval_;
friend class SeventvEventAPI; friend SeventvEventAPI;
}; };
} // namespace chatterino } // namespace chatterino::seventv::eventapi
@@ -1,21 +1,20 @@
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include <utility> #include <utility>
namespace chatterino { namespace chatterino::seventv::eventapi {
SeventvEventAPIDispatch::SeventvEventAPIDispatch(QJsonObject obj) Dispatch::Dispatch(QJsonObject obj)
: type(magic_enum::enum_cast<SeventvEventAPISubscriptionType>( : type(magic_enum::enum_cast<SubscriptionType>(
obj["type"].toString().toStdString()) obj["type"].toString().toStdString())
.value_or(SeventvEventAPISubscriptionType::INVALID)) .value_or(SubscriptionType::INVALID))
, body(obj["body"].toObject()) , body(obj["body"].toObject())
, id(this->body["id"].toString()) , id(this->body["id"].toString())
, actorName(this->body["actor"].toObject()["display_name"].toString()) , actorName(this->body["actor"].toObject()["display_name"].toString())
{ {
} }
SeventvEventAPIEmoteAddDispatch::SeventvEventAPIEmoteAddDispatch( EmoteAddDispatch::EmoteAddDispatch(const Dispatch &dispatch, QJsonObject emote)
const SeventvEventAPIDispatch &dispatch, QJsonObject emote)
: emoteSetID(dispatch.id) : emoteSetID(dispatch.id)
, actorName(dispatch.actorName) , actorName(dispatch.actorName)
, emoteJson(std::move(emote)) , emoteJson(std::move(emote))
@@ -23,7 +22,7 @@ SeventvEventAPIEmoteAddDispatch::SeventvEventAPIEmoteAddDispatch(
{ {
} }
bool SeventvEventAPIEmoteAddDispatch::validate() const bool EmoteAddDispatch::validate() const
{ {
bool validValues = bool validValues =
!this->emoteSetID.isEmpty() && !this->emoteJson.isEmpty(); !this->emoteSetID.isEmpty() && !this->emoteJson.isEmpty();
@@ -43,8 +42,8 @@ bool SeventvEventAPIEmoteAddDispatch::validate() const
emoteData.contains("owner"); emoteData.contains("owner");
} }
SeventvEventAPIEmoteRemoveDispatch::SeventvEventAPIEmoteRemoveDispatch( EmoteRemoveDispatch::EmoteRemoveDispatch(const Dispatch &dispatch,
const SeventvEventAPIDispatch &dispatch, QJsonObject emote) QJsonObject emote)
: emoteSetID(dispatch.id) : emoteSetID(dispatch.id)
, actorName(dispatch.actorName) , actorName(dispatch.actorName)
, emoteName(emote["name"].toString()) , emoteName(emote["name"].toString())
@@ -52,15 +51,15 @@ SeventvEventAPIEmoteRemoveDispatch::SeventvEventAPIEmoteRemoveDispatch(
{ {
} }
bool SeventvEventAPIEmoteRemoveDispatch::validate() const bool EmoteRemoveDispatch::validate() const
{ {
return !this->emoteSetID.isEmpty() && !this->emoteName.isEmpty() && return !this->emoteSetID.isEmpty() && !this->emoteName.isEmpty() &&
!this->emoteID.isEmpty(); !this->emoteID.isEmpty();
} }
SeventvEventAPIEmoteUpdateDispatch::SeventvEventAPIEmoteUpdateDispatch( EmoteUpdateDispatch::EmoteUpdateDispatch(const Dispatch &dispatch,
const SeventvEventAPIDispatch &dispatch, QJsonObject oldValue, QJsonObject oldValue,
QJsonObject value) QJsonObject value)
: emoteSetID(dispatch.id) : emoteSetID(dispatch.id)
, actorName(dispatch.actorName) , actorName(dispatch.actorName)
, emoteID(value["id"].toString()) , emoteID(value["id"].toString())
@@ -69,17 +68,15 @@ SeventvEventAPIEmoteUpdateDispatch::SeventvEventAPIEmoteUpdateDispatch(
{ {
} }
bool SeventvEventAPIEmoteUpdateDispatch::validate() const bool EmoteUpdateDispatch::validate() const
{ {
return !this->emoteSetID.isEmpty() && !this->emoteID.isEmpty() && return !this->emoteSetID.isEmpty() && !this->emoteID.isEmpty() &&
!this->oldEmoteName.isEmpty() && !this->emoteName.isEmpty() && !this->oldEmoteName.isEmpty() && !this->emoteName.isEmpty() &&
this->oldEmoteName != this->emoteName; this->oldEmoteName != this->emoteName;
} }
SeventvEventAPIUserConnectionUpdateDispatch:: UserConnectionUpdateDispatch::UserConnectionUpdateDispatch(
SeventvEventAPIUserConnectionUpdateDispatch( const Dispatch &dispatch, const QJsonObject &update, size_t connectionIndex)
const SeventvEventAPIDispatch &dispatch, const QJsonObject &update,
size_t connectionIndex)
: userID(dispatch.id) : userID(dispatch.id)
, actorName(dispatch.actorName) , actorName(dispatch.actorName)
, oldEmoteSetID(update["old_value"].toObject()["id"].toString()) , oldEmoteSetID(update["old_value"].toObject()["id"].toString())
@@ -88,10 +85,10 @@ SeventvEventAPIUserConnectionUpdateDispatch::
{ {
} }
bool SeventvEventAPIUserConnectionUpdateDispatch::validate() const bool UserConnectionUpdateDispatch::validate() const
{ {
return !this->userID.isEmpty() && !this->oldEmoteSetID.isEmpty() && return !this->userID.isEmpty() && !this->oldEmoteSetID.isEmpty() &&
!this->emoteSetID.isEmpty(); !this->emoteSetID.isEmpty();
} }
} // namespace chatterino } // namespace chatterino::seventv::eventapi
@@ -0,0 +1,70 @@
#pragma once
#include "providers/seventv/eventapi/Subscription.hpp"
#include <QJsonObject>
#include <QString>
namespace chatterino::seventv::eventapi {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#message-payload
struct Dispatch {
SubscriptionType type;
QJsonObject body;
QString id;
// it's okay for this to be empty
QString actorName;
Dispatch(QJsonObject obj);
};
struct EmoteAddDispatch {
QString emoteSetID;
QString actorName;
QJsonObject emoteJson;
QString emoteID;
EmoteAddDispatch(const Dispatch &dispatch, QJsonObject emote);
bool validate() const;
};
struct EmoteRemoveDispatch {
QString emoteSetID;
QString actorName;
QString emoteName;
QString emoteID;
EmoteRemoveDispatch(const Dispatch &dispatch, QJsonObject emote);
bool validate() const;
};
struct EmoteUpdateDispatch {
QString emoteSetID;
QString actorName;
QString emoteID;
QString oldEmoteName;
QString emoteName;
EmoteUpdateDispatch(const Dispatch &dispatch, QJsonObject oldValue,
QJsonObject value);
bool validate() const;
};
struct UserConnectionUpdateDispatch {
QString userID;
QString actorName;
QString oldEmoteSetID;
QString emoteSetID;
size_t connectionIndex;
UserConnectionUpdateDispatch(const Dispatch &dispatch,
const QJsonObject &update,
size_t connectionIndex);
bool validate() const;
};
} // namespace chatterino::seventv::eventapi
@@ -0,0 +1,11 @@
#include "providers/seventv/eventapi/Message.hpp"
namespace chatterino::seventv::eventapi {
Message::Message(QJsonObject _json)
: data(_json["d"].toObject())
, op(Opcode(_json["op"].toInt()))
{
}
} // namespace chatterino::seventv::eventapi
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp" #include "providers/seventv/eventapi/Subscription.hpp"
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <magic_enum.hpp> #include <magic_enum.hpp>
@@ -8,27 +8,26 @@
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
namespace chatterino { namespace chatterino::seventv::eventapi {
struct SeventvEventAPIMessage { struct Message {
QJsonObject data; QJsonObject data;
SeventvEventAPIOpcode op; Opcode op;
SeventvEventAPIMessage(QJsonObject _json); Message(QJsonObject _json);
template <class InnerClass> template <class InnerClass>
boost::optional<InnerClass> toInner(); boost::optional<InnerClass> toInner();
}; };
template <class InnerClass> template <class InnerClass>
boost::optional<InnerClass> SeventvEventAPIMessage::toInner() boost::optional<InnerClass> Message::toInner()
{ {
return InnerClass{this->data}; return InnerClass{this->data};
} }
static boost::optional<SeventvEventAPIMessage> parseSeventvEventAPIBaseMessage( static boost::optional<Message> parseBaseMessage(const QString &blob)
const QString &blob)
{ {
QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8())); QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8()));
@@ -37,7 +36,7 @@ static boost::optional<SeventvEventAPIMessage> parseSeventvEventAPIBaseMessage(
return boost::none; return boost::none;
} }
return SeventvEventAPIMessage(jsonDoc.object()); return Message(jsonDoc.object());
} }
} // namespace chatterino } // namespace chatterino::seventv::eventapi
@@ -1,72 +0,0 @@
#pragma once
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include <QJsonObject>
#include <QString>
namespace chatterino {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#message-payload
struct SeventvEventAPIDispatch {
SeventvEventAPISubscriptionType type;
QJsonObject body;
QString id;
// it's okay for this to be empty
QString actorName;
SeventvEventAPIDispatch(QJsonObject obj);
};
struct SeventvEventAPIEmoteAddDispatch {
QString emoteSetID;
QString actorName;
QJsonObject emoteJson;
QString emoteID;
SeventvEventAPIEmoteAddDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject emote);
bool validate() const;
};
struct SeventvEventAPIEmoteRemoveDispatch {
QString emoteSetID;
QString actorName;
QString emoteName;
QString emoteID;
SeventvEventAPIEmoteRemoveDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject emote);
bool validate() const;
};
struct SeventvEventAPIEmoteUpdateDispatch {
QString emoteSetID;
QString actorName;
QString emoteID;
QString oldEmoteName;
QString emoteName;
SeventvEventAPIEmoteUpdateDispatch(const SeventvEventAPIDispatch &dispatch,
QJsonObject oldValue, QJsonObject value);
bool validate() const;
};
struct SeventvEventAPIUserConnectionUpdateDispatch {
QString userID;
QString actorName;
QString oldEmoteSetID;
QString emoteSetID;
size_t connectionIndex;
SeventvEventAPIUserConnectionUpdateDispatch(
const SeventvEventAPIDispatch &dispatch, const QJsonObject &update,
size_t connectionIndex);
bool validate() const;
};
} // namespace chatterino
@@ -1,11 +0,0 @@
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp"
namespace chatterino {
SeventvEventAPIMessage::SeventvEventAPIMessage(QJsonObject _json)
: data(_json["d"].toObject())
, op(SeventvEventAPIOpcode(_json["op"].toInt()))
{
}
} // namespace chatterino
@@ -1,80 +0,0 @@
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <tuple>
namespace {
using namespace chatterino;
const char *typeToString(SeventvEventAPISubscriptionType type)
{
switch (type)
{
case SeventvEventAPISubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case SeventvEventAPISubscriptionType::UpdateUser:
return "user.update";
default:
return "";
}
}
QJsonObject createDataJson(const char *typeName, const QString &condition)
{
QJsonObject data;
data["type"] = typeName;
{
QJsonObject conditionObj;
conditionObj["object_id"] = condition;
data["condition"] = conditionObj;
}
return data;
}
} // namespace
namespace chatterino {
bool SeventvEventAPISubscription::operator==(
const SeventvEventAPISubscription &rhs) const
{
return std::tie(this->condition, this->type) ==
std::tie(rhs.condition, rhs.type);
}
bool SeventvEventAPISubscription::operator!=(
const SeventvEventAPISubscription &rhs) const
{
return !(rhs == *this);
}
QByteArray SeventvEventAPISubscription::encodeSubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)SeventvEventAPIOpcode::Subscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QByteArray SeventvEventAPISubscription::encodeUnsubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)SeventvEventAPIOpcode::Unsubscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QDebug &operator<<(QDebug &dbg, const SeventvEventAPISubscription &subscription)
{
dbg << "SeventvEventAPISubscription{ condition:" << subscription.condition
<< "type:" << (int)subscription.type << '}';
return dbg;
}
} // namespace chatterino
@@ -1,76 +0,0 @@
#pragma once
#include <magic_enum.hpp>
#include <QByteArray>
#include <QHash>
#include <QString>
namespace chatterino {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#subscription-types
enum class SeventvEventAPISubscriptionType {
UpdateEmoteSet,
UpdateUser,
INVALID,
};
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#opcodes
enum class SeventvEventAPIOpcode {
Dispatch = 0,
Hello = 1,
Heartbeat = 2,
Reconnect = 4,
Ack = 5,
Error = 6,
EndOfStream = 7,
Identify = 33,
Resume = 34,
Subscribe = 35,
Unsubscribe = 36,
Signal = 37,
};
struct SeventvEventAPISubscription {
bool operator==(const SeventvEventAPISubscription &rhs) const;
bool operator!=(const SeventvEventAPISubscription &rhs) const;
QString condition;
SeventvEventAPISubscriptionType type;
QByteArray encodeSubscribe() const;
QByteArray encodeUnsubscribe() const;
friend QDebug &operator<<(QDebug &dbg,
const SeventvEventAPISubscription &subscription);
};
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::SeventvEventAPISubscriptionType>(
chatterino::SeventvEventAPISubscriptionType value) noexcept
{
switch (value)
{
case chatterino::SeventvEventAPISubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case chatterino::SeventvEventAPISubscriptionType::UpdateUser:
return "user.update";
default:
return default_tag;
}
}
namespace std {
template <>
struct hash<chatterino::SeventvEventAPISubscription> {
size_t operator()(const chatterino::SeventvEventAPISubscription &sub) const
{
return (size_t)qHash(sub.condition, qHash((int)sub.type));
}
};
} // namespace std
@@ -0,0 +1,105 @@
#include "providers/seventv/eventapi/Subscription.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <tuple>
#include <utility>
namespace {
using namespace chatterino::seventv::eventapi;
const char *typeToString(SubscriptionType type)
{
return magic_enum::enum_name(type).data();
}
QJsonObject createDataJson(const char *typeName, const Condition &condition)
{
QJsonObject data;
data["type"] = typeName;
data["condition"] = std::visit(
[](const auto &c) {
return c.encode();
},
condition);
return data;
}
} // namespace
namespace chatterino::seventv::eventapi {
bool Subscription::operator==(const Subscription &rhs) const
{
return std::tie(this->condition, this->type) ==
std::tie(rhs.condition, rhs.type);
}
bool Subscription::operator!=(const Subscription &rhs) const
{
return !(rhs == *this);
}
QByteArray Subscription::encodeSubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Subscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QByteArray Subscription::encodeUnsubscribe() const
{
const auto *typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Unsubscribe;
root["d"] = createDataJson(typeName, this->condition);
return QJsonDocument(root).toJson();
}
QDebug &operator<<(QDebug &dbg, const Subscription &subscription)
{
std::visit(
[&](const auto &cond) {
dbg << "Subscription{ condition:" << cond
<< "type:" << magic_enum::enum_name(subscription.type).data()
<< '}';
},
subscription.condition);
return dbg;
}
ObjectIDCondition::ObjectIDCondition(QString objectID)
: objectID(std::move(objectID))
{
}
QJsonObject ObjectIDCondition::encode() const
{
QJsonObject obj;
obj["object_id"] = this->objectID;
return obj;
}
bool ObjectIDCondition::operator==(const ObjectIDCondition &rhs) const
{
return this->objectID == rhs.objectID;
}
bool ObjectIDCondition::operator!=(const ObjectIDCondition &rhs) const
{
return !(*this == rhs);
}
QDebug &operator<<(QDebug &dbg, const ObjectIDCondition &condition)
{
dbg << "{ objectID:" << condition.objectID << "}";
return dbg;
}
} // namespace chatterino::seventv::eventapi
@@ -0,0 +1,106 @@
#pragma once
#include <magic_enum.hpp>
#include <QByteArray>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include <variant>
namespace chatterino::seventv::eventapi {
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#subscription-types
enum class SubscriptionType {
UpdateEmoteSet,
UpdateUser,
INVALID,
};
// https://github.com/SevenTV/EventAPI/tree/ca4ff15cc42b89560fa661a76c5849047763d334#opcodes
enum class Opcode {
Dispatch = 0,
Hello = 1,
Heartbeat = 2,
Reconnect = 4,
Ack = 5,
Error = 6,
EndOfStream = 7,
Identify = 33,
Resume = 34,
Subscribe = 35,
Unsubscribe = 36,
Signal = 37,
};
struct ObjectIDCondition {
ObjectIDCondition(QString objectID);
QString objectID;
QJsonObject encode() const;
friend QDebug &operator<<(QDebug &dbg, const ObjectIDCondition &condition);
bool operator==(const ObjectIDCondition &rhs) const;
bool operator!=(const ObjectIDCondition &rhs) const;
};
using Condition = std::variant<ObjectIDCondition>;
struct Subscription {
bool operator==(const Subscription &rhs) const;
bool operator!=(const Subscription &rhs) const;
Condition condition;
SubscriptionType type;
QByteArray encodeSubscribe() const;
QByteArray encodeUnsubscribe() const;
friend QDebug &operator<<(QDebug &dbg, const Subscription &subscription);
};
} // namespace chatterino::seventv::eventapi
template <>
constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
chatterino::seventv::eventapi::SubscriptionType>(
chatterino::seventv::eventapi::SubscriptionType value) noexcept
{
using chatterino::seventv::eventapi::SubscriptionType;
switch (value)
{
case SubscriptionType::UpdateEmoteSet:
return "emote_set.update";
case SubscriptionType::UpdateUser:
return "user.update";
default:
return default_tag;
}
}
namespace std {
template <>
struct hash<chatterino::seventv::eventapi::ObjectIDCondition> {
size_t operator()(
const chatterino::seventv::eventapi::ObjectIDCondition &c) const
{
return (size_t)qHash(c.objectID);
}
};
template <>
struct hash<chatterino::seventv::eventapi::Subscription> {
size_t operator()(
const chatterino::seventv::eventapi::Subscription &sub) const
{
const size_t conditionHash =
std::hash<chatterino::seventv::eventapi::Condition>{}(
sub.condition);
return (size_t)qHash(conditionHash, qHash((int)sub.type));
}
};
} // namespace std
+16 -15
View File
@@ -18,7 +18,7 @@
#include "providers/bttv/BttvLiveUpdates.hpp" #include "providers/bttv/BttvLiveUpdates.hpp"
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp" #include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
#include "providers/RecentMessagesApi.hpp" #include "providers/RecentMessagesApi.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/SeventvEmotes.hpp" #include "providers/seventv/SeventvEmotes.hpp"
#include "providers/seventv/SeventvEventAPI.hpp" #include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/api/Helix.hpp" #include "providers/twitch/api/Helix.hpp"
@@ -121,16 +121,6 @@ TwitchChannel::TwitchChannel(const QString &name)
this->loadRecentMessagesReconnect(); this->loadRecentMessagesReconnect();
}); });
this->destroyed.connect([this]() {
getApp()->twitch->dropSeventvChannel(this->seventvUserID_,
this->seventvEmoteSetID_);
if (getApp()->twitch->bttvLiveUpdates)
{
getApp()->twitch->bttvLiveUpdates->partChannel(this->roomId());
}
});
this->messageRemovedFromStart.connect([this](MessagePtr &msg) { this->messageRemovedFromStart.connect([this](MessagePtr &msg) {
if (msg->replyThread) if (msg->replyThread)
{ {
@@ -169,6 +159,17 @@ TwitchChannel::TwitchChannel(const QString &name)
#endif #endif
} }
TwitchChannel::~TwitchChannel()
{
getApp()->twitch->dropSeventvChannel(this->seventvUserID_,
this->seventvEmoteSetID_);
if (getApp()->twitch->bttvLiveUpdates)
{
getApp()->twitch->bttvLiveUpdates->partChannel(this->roomId());
}
}
void TwitchChannel::initialize() void TwitchChannel::initialize()
{ {
this->fetchDisplayName(); this->fetchDisplayName();
@@ -691,7 +692,7 @@ void TwitchChannel::removeBttvEmote(
} }
void TwitchChannel::addSeventvEmote( void TwitchChannel::addSeventvEmote(
const SeventvEventAPIEmoteAddDispatch &dispatch) const seventv::eventapi::EmoteAddDispatch &dispatch)
{ {
if (!SeventvEmotes::addEmote(this->seventvEmotes_, dispatch)) if (!SeventvEmotes::addEmote(this->seventvEmotes_, dispatch))
{ {
@@ -703,7 +704,7 @@ void TwitchChannel::addSeventvEmote(
} }
void TwitchChannel::updateSeventvEmote( void TwitchChannel::updateSeventvEmote(
const SeventvEventAPIEmoteUpdateDispatch &dispatch) const seventv::eventapi::EmoteUpdateDispatch &dispatch)
{ {
if (!SeventvEmotes::updateEmote(this->seventvEmotes_, dispatch)) if (!SeventvEmotes::updateEmote(this->seventvEmotes_, dispatch))
{ {
@@ -717,7 +718,7 @@ void TwitchChannel::updateSeventvEmote(
} }
void TwitchChannel::removeSeventvEmote( void TwitchChannel::removeSeventvEmote(
const SeventvEventAPIEmoteRemoveDispatch &dispatch) const seventv::eventapi::EmoteRemoveDispatch &dispatch)
{ {
auto removed = SeventvEmotes::removeEmote(this->seventvEmotes_, dispatch); auto removed = SeventvEmotes::removeEmote(this->seventvEmotes_, dispatch);
if (!removed) if (!removed)
@@ -730,7 +731,7 @@ void TwitchChannel::removeSeventvEmote(
} }
void TwitchChannel::updateSeventvUser( void TwitchChannel::updateSeventvUser(
const SeventvEventAPIUserConnectionUpdateDispatch &dispatch) const seventv::eventapi::UserConnectionUpdateDispatch &dispatch)
{ {
if (dispatch.connectionIndex != this->seventvUserTwitchConnectionIndex_) if (dispatch.connectionIndex != this->seventvUserTwitchConnectionIndex_)
{ {
+15 -8
View File
@@ -49,11 +49,15 @@ class FfzEmotes;
class BttvEmotes; class BttvEmotes;
struct BttvLiveUpdateEmoteUpdateAddMessage; struct BttvLiveUpdateEmoteUpdateAddMessage;
struct BttvLiveUpdateEmoteRemoveMessage; struct BttvLiveUpdateEmoteRemoveMessage;
class SeventvEmotes; class SeventvEmotes;
struct SeventvEventAPIEmoteAddDispatch; namespace seventv::eventapi {
struct SeventvEventAPIEmoteUpdateDispatch; struct EmoteAddDispatch;
struct SeventvEventAPIEmoteRemoveDispatch; struct EmoteUpdateDispatch;
struct SeventvEventAPIUserConnectionUpdateDispatch; struct EmoteRemoveDispatch;
struct UserConnectionUpdateDispatch;
} // namespace seventv::eventapi
struct ChannelPointReward; struct ChannelPointReward;
class MessageThread; class MessageThread;
struct CheerEmoteSet; struct CheerEmoteSet;
@@ -98,6 +102,7 @@ public:
}; };
explicit TwitchChannel(const QString &channelName); explicit TwitchChannel(const QString &channelName);
~TwitchChannel() override;
void initialize(); void initialize();
@@ -149,14 +154,16 @@ public:
void removeBttvEmote(const BttvLiveUpdateEmoteRemoveMessage &message); void removeBttvEmote(const BttvLiveUpdateEmoteRemoveMessage &message);
/** Adds a 7TV channel emote to this channel. */ /** Adds a 7TV channel emote to this channel. */
void addSeventvEmote(const SeventvEventAPIEmoteAddDispatch &dispatch); void addSeventvEmote(const seventv::eventapi::EmoteAddDispatch &dispatch);
/** Updates a 7TV channel emote's name in this channel */ /** Updates a 7TV channel emote's name in this channel */
void updateSeventvEmote(const SeventvEventAPIEmoteUpdateDispatch &dispatch); void updateSeventvEmote(
const seventv::eventapi::EmoteUpdateDispatch &dispatch);
/** Removes a 7TV channel emote from this channel */ /** Removes a 7TV channel emote from this channel */
void removeSeventvEmote(const SeventvEventAPIEmoteRemoveDispatch &dispatch); void removeSeventvEmote(
const seventv::eventapi::EmoteRemoveDispatch &dispatch);
/** Updates the current 7TV user. Currently, only the emote-set is updated. */ /** Updates the current 7TV user. Currently, only the emote-set is updated. */
void updateSeventvUser( void updateSeventvUser(
const SeventvEventAPIUserConnectionUpdateDispatch &dispatch); const seventv::eventapi::UserConnectionUpdateDispatch &dispatch);
// Update the channel's 7TV information (the channel's 7TV user ID and emote set ID) // Update the channel's 7TV information (the channel's 7TV user ID and emote set ID)
void updateSeventvData(const QString &newUserID, void updateSeventvData(const QString &newUserID,
+1 -1
View File
@@ -7,7 +7,7 @@
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp" #include "providers/bttv/BttvLiveUpdates.hpp"
#include "providers/seventv/eventapi/SeventvEventAPISubscription.hpp" #include "providers/seventv/eventapi/Subscription.hpp"
#include "providers/seventv/SeventvEventAPI.hpp" #include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/api/Helix.hpp" #include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/ChannelPointReward.hpp"
+8 -7
View File
@@ -1,14 +1,15 @@
#include "providers/seventv/SeventvEventAPI.hpp" #include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIClient.hpp" #include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIDispatch.hpp" #include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/SeventvEventAPIMessage.hpp" #include "providers/seventv/eventapi/Message.hpp"
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <QString> #include <QString>
using namespace chatterino; using namespace chatterino;
using namespace chatterino::seventv::eventapi;
using namespace std::chrono_literals; using namespace std::chrono_literals;
const QString EMOTE_SET_A = "60b39e943e203cc169dfc106"; const QString EMOTE_SET_A = "60b39e943e203cc169dfc106";
@@ -21,10 +22,10 @@ TEST(SeventvEventAPI, AllEvents)
auto *eventAPI = new SeventvEventAPI(host, std::chrono::milliseconds(1000)); auto *eventAPI = new SeventvEventAPI(host, std::chrono::milliseconds(1000));
eventAPI->start(); eventAPI->start();
boost::optional<SeventvEventAPIEmoteAddDispatch> addDispatch; boost::optional<EmoteAddDispatch> addDispatch;
boost::optional<SeventvEventAPIEmoteUpdateDispatch> updateDispatch; boost::optional<EmoteUpdateDispatch> updateDispatch;
boost::optional<SeventvEventAPIEmoteRemoveDispatch> removeDispatch; boost::optional<EmoteRemoveDispatch> removeDispatch;
boost::optional<SeventvEventAPIUserConnectionUpdateDispatch> userDispatch; boost::optional<UserConnectionUpdateDispatch> userDispatch;
eventAPI->signals_.emoteAdded.connect([&](const auto &d) { eventAPI->signals_.emoteAdded.connect([&](const auto &d) {
addDispatch = d; addDispatch = d;