feat: show BTTV Pro badges (#6625)
Reviewed-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
#include "providers/bttv/BttvBadges.hpp"
|
||||
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
QString BttvBadges::idForBadge(const QJsonObject &badgeJson) const
|
||||
{
|
||||
return badgeJson["url"].toString();
|
||||
}
|
||||
|
||||
EmotePtr BttvBadges::createBadge(const QString &id,
|
||||
const QJsonObject & /* badgeJson */) const
|
||||
{
|
||||
auto emote = Emote{
|
||||
.name = EmoteName{},
|
||||
.images = ImageSet(Image::fromUrl(Url{id}, 18.0 / 72.0)),
|
||||
.tooltip = Tooltip{"BTTV Pro"},
|
||||
.homePage = Url{},
|
||||
.id = EmoteId{id},
|
||||
};
|
||||
|
||||
return std::make_shared<const Emote>(std::move(emote));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/BadgeRegistry.hpp"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
class BttvBadges : public BadgeRegistry
|
||||
{
|
||||
public:
|
||||
BttvBadges() = default;
|
||||
|
||||
protected:
|
||||
QString idForBadge(const QJsonObject &badgeJson) const override;
|
||||
EmotePtr createBadge(const QString &id,
|
||||
const QJsonObject &badgeJson) const override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
BttvLiveUpdatesPrivate &operator=(const BttvLiveUpdatesPrivate &&) = delete;
|
||||
|
||||
std::shared_ptr<BttvLiveUpdateClient> makeClient();
|
||||
std::shared_ptr<BttvLiveUpdateClient> anyClient();
|
||||
|
||||
// Contains all joined Twitch channel-ids
|
||||
std::unordered_set<QString> joinedChannels;
|
||||
@@ -50,6 +51,15 @@ std::shared_ptr<BttvLiveUpdateClient> BttvLiveUpdatesPrivate::makeClient()
|
||||
return std::make_shared<BttvLiveUpdateClient>(this->parent);
|
||||
}
|
||||
|
||||
std::shared_ptr<BttvLiveUpdateClient> BttvLiveUpdatesPrivate::anyClient()
|
||||
{
|
||||
if (this->clients().empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return this->clients().begin()->second;
|
||||
}
|
||||
|
||||
BttvLiveUpdates::BttvLiveUpdates(QString host)
|
||||
: private_(std::make_unique<BttvLiveUpdatesPrivate>(*this, std::move(host)))
|
||||
{
|
||||
@@ -77,6 +87,16 @@ void BttvLiveUpdates::partChannel(const QString &id)
|
||||
}
|
||||
}
|
||||
|
||||
void BttvLiveUpdates::broadcastMe(const QString &channelID,
|
||||
const QString &userID)
|
||||
{
|
||||
auto client = this->private_->anyClient();
|
||||
if (client)
|
||||
{
|
||||
client->broadcastMe(channelID, userID);
|
||||
}
|
||||
}
|
||||
|
||||
void BttvLiveUpdates::stop()
|
||||
{
|
||||
this->private_->stop();
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
*/
|
||||
void joinChannel(const QString &channelID, const QString &userID);
|
||||
|
||||
void broadcastMe(const QString &channelID, const QString &userID);
|
||||
|
||||
/**
|
||||
* Parts a twitch channel by its id (without any prefix like 'twitch:')
|
||||
* if it's joined.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#include "providers/bttv/liveupdates/BttvLiveUpdateClient.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/bttv/BttvBadges.hpp"
|
||||
#include "providers/bttv/BttvLiveUpdates.hpp"
|
||||
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QStringBuilder>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
@@ -67,7 +70,25 @@ void BttvLiveUpdateClient::onMessage(const QByteArray &msg)
|
||||
}
|
||||
else if (eventType == "lookup_user")
|
||||
{
|
||||
// ignored
|
||||
auto message = BttvLiveUpdateUserUpdateMessage(eventData);
|
||||
if (!message.validate())
|
||||
{
|
||||
qCDebug(chatterinoBttv) << "Invalid user update message" << json;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!message.hasBadge())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto *app = tryGetApp();
|
||||
if (app)
|
||||
{
|
||||
auto *bttvBadges = app->getBttvBadges();
|
||||
auto badgeID = bttvBadges->registerBadge(message.badgeObject);
|
||||
bttvBadges->assignBadgeToUser(badgeID, UserId{message.userID});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -75,4 +96,21 @@ void BttvLiveUpdateClient::onMessage(const QByteArray &msg)
|
||||
}
|
||||
}
|
||||
|
||||
void BttvLiveUpdateClient::broadcastMe(const QString &channelID,
|
||||
const QString &userID)
|
||||
{
|
||||
QJsonObject obj{
|
||||
{"name", "broadcast_me"},
|
||||
{"data",
|
||||
QJsonObject{
|
||||
{"provider", "twitch"},
|
||||
{"providerId", userID},
|
||||
{"channel", QString(u"twitch:" % channelID)},
|
||||
}},
|
||||
};
|
||||
QJsonDocument doc(obj);
|
||||
|
||||
this->sendText(doc.toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -15,6 +15,8 @@ public:
|
||||
|
||||
void onMessage(const QByteArray &msg) /* override */;
|
||||
|
||||
void broadcastMe(const QString &channelID, const QString &userID);
|
||||
|
||||
private:
|
||||
BttvLiveUpdates &manager;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Qt::Literals;
|
||||
|
||||
bool tryParseChannelId(QString &channelId)
|
||||
{
|
||||
if (!channelId.startsWith("twitch:"))
|
||||
@@ -49,4 +51,21 @@ bool BttvLiveUpdateEmoteRemoveMessage::validate() const
|
||||
!this->channelID.isEmpty();
|
||||
}
|
||||
|
||||
BttvLiveUpdateUserUpdateMessage::BttvLiveUpdateUserUpdateMessage(
|
||||
const QJsonObject &json)
|
||||
: userID(json["providerId"_L1].toString())
|
||||
, badgeObject(json["badge"_L1].toObject())
|
||||
{
|
||||
}
|
||||
|
||||
bool BttvLiveUpdateUserUpdateMessage::validate() const
|
||||
{
|
||||
return !this->userID.isEmpty();
|
||||
}
|
||||
|
||||
bool BttvLiveUpdateUserUpdateMessage::hasBadge() const
|
||||
{
|
||||
return !this->badgeObject.isEmpty();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -35,4 +35,14 @@ private:
|
||||
bool badChannelID_;
|
||||
};
|
||||
|
||||
struct BttvLiveUpdateUserUpdateMessage {
|
||||
BttvLiveUpdateUserUpdateMessage(const QJsonObject &json);
|
||||
|
||||
QString userID;
|
||||
QJsonObject badgeObject;
|
||||
|
||||
bool validate() const;
|
||||
bool hasBadge() const;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -61,6 +61,11 @@ public:
|
||||
this->ws_.close();
|
||||
}
|
||||
|
||||
void sendText(const QByteArray &data)
|
||||
{
|
||||
this->ws_.sendText(data);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @return true if this client subscribed to this subscription
|
||||
|
||||
@@ -4,74 +4,30 @@
|
||||
#include "messages/Image.hpp"
|
||||
#include "providers/seventv/SeventvEmotes.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
std::optional<EmotePtr> SeventvBadges::getBadge(const UserId &id) const
|
||||
QString SeventvBadges::idForBadge(const QJsonObject &badgeJson) const
|
||||
{
|
||||
std::shared_lock lock(this->mutex_);
|
||||
|
||||
auto it = this->badgeMap_.find(id.string);
|
||||
if (it != this->badgeMap_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
return badgeJson["id"].toString();
|
||||
}
|
||||
|
||||
void SeventvBadges::assignBadgeToUser(const QString &badgeID,
|
||||
const UserId &userID)
|
||||
EmotePtr SeventvBadges::createBadge(const QString &id,
|
||||
const QJsonObject &badgeJson) const
|
||||
{
|
||||
const std::unique_lock lock(this->mutex_);
|
||||
|
||||
const auto badgeIt = this->knownBadges_.find(badgeID);
|
||||
if (badgeIt != this->knownBadges_.end())
|
||||
{
|
||||
this->badgeMap_[userID.string] = badgeIt->second;
|
||||
}
|
||||
}
|
||||
|
||||
void SeventvBadges::clearBadgeFromUser(const QString &badgeID,
|
||||
const UserId &userID)
|
||||
{
|
||||
const std::unique_lock lock(this->mutex_);
|
||||
|
||||
const auto it = this->badgeMap_.find(userID.string);
|
||||
if (it != this->badgeMap_.end() && it->second->id.string == badgeID)
|
||||
{
|
||||
this->badgeMap_.erase(userID.string);
|
||||
}
|
||||
}
|
||||
|
||||
void SeventvBadges::registerBadge(const QJsonObject &badgeJson)
|
||||
{
|
||||
const auto badgeID = badgeJson["id"].toString();
|
||||
|
||||
const std::unique_lock lock(this->mutex_);
|
||||
|
||||
if (this->knownBadges_.find(badgeID) != this->knownBadges_.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto emote = Emote{
|
||||
.name = EmoteName{},
|
||||
.images = SeventvEmotes::createImageSet(badgeJson, true),
|
||||
.tooltip = Tooltip{badgeJson["tooltip"].toString()},
|
||||
.homePage = Url{},
|
||||
.id = EmoteId{badgeID},
|
||||
.id = EmoteId{id},
|
||||
};
|
||||
|
||||
if (emote.images.getImage1()->isEmpty())
|
||||
{
|
||||
return; // Bad images
|
||||
return nullptr; // Bad images
|
||||
}
|
||||
|
||||
this->knownBadges_[badgeID] =
|
||||
std::make_shared<const Emote>(std::move(emote));
|
||||
return std::make_shared<const Emote>(std::move(emote));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,44 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
#include "util/BadgeRegistry.hpp"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
class SeventvBadges
|
||||
class SeventvBadges : public BadgeRegistry
|
||||
{
|
||||
public:
|
||||
// Return the badge, if any, that is assigned to the user
|
||||
std::optional<EmotePtr> getBadge(const UserId &id) const;
|
||||
SeventvBadges() = default;
|
||||
|
||||
// Assign the given badge to the user
|
||||
void assignBadgeToUser(const QString &badgeID, const UserId &userID);
|
||||
|
||||
// Remove the given badge from the user
|
||||
void clearBadgeFromUser(const QString &badgeID, const UserId &userID);
|
||||
|
||||
// Register a new known badge
|
||||
// The json object will contain all information about the badge, like its ID & its images
|
||||
void registerBadge(const QJsonObject &badgeJson);
|
||||
|
||||
private:
|
||||
// Mutex for both `badgeMap_` and `knownBadges_`
|
||||
mutable std::shared_mutex mutex_;
|
||||
|
||||
// user-id => badge
|
||||
std::unordered_map<QString, EmotePtr> badgeMap_;
|
||||
// badge-id => badge
|
||||
std::unordered_map<QString, EmotePtr> knownBadges_;
|
||||
protected:
|
||||
QString idForBadge(const QJsonObject &badgeJson) const override;
|
||||
EmotePtr createBadge(const QString &id,
|
||||
const QJsonObject &badgeJson) const override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -831,6 +831,7 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||
|
||||
bool messageSent = false;
|
||||
this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent);
|
||||
this->updateBttvActivity();
|
||||
this->updateSevenTVActivity();
|
||||
|
||||
if (messageSent)
|
||||
@@ -2256,6 +2257,31 @@ std::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string) const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void TwitchChannel::updateBttvActivity()
|
||||
{
|
||||
if (!getApp()->getBttvLiveUpdates())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto now = QDateTime::currentDateTimeUtc();
|
||||
if (this->nextBttvActivity_.isValid() && now < this->nextBttvActivity_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->nextBttvActivity_ = now.addSecs(60);
|
||||
|
||||
qCDebug(chatterinoBttv) << "Sending activity in" << this->getName();
|
||||
|
||||
auto acc = getApp()->getAccounts()->twitch.getCurrent();
|
||||
if (acc->isAnon())
|
||||
{
|
||||
return;
|
||||
}
|
||||
getApp()->getBttvLiveUpdates()->broadcastMe(this->roomId(),
|
||||
acc->getUserId());
|
||||
}
|
||||
|
||||
void TwitchChannel::updateSevenTVActivity()
|
||||
{
|
||||
static const QString seventvActivityUrl =
|
||||
|
||||
@@ -377,6 +377,9 @@ private:
|
||||
|
||||
/** Joins (subscribes to) a Twitch channel for updates on BTTV. */
|
||||
void joinBttvChannel() const;
|
||||
|
||||
void updateBttvActivity();
|
||||
|
||||
/**
|
||||
* Indicates an activity to 7TV in this channel for this user.
|
||||
* This is done at most once every 60s.
|
||||
@@ -514,6 +517,8 @@ private:
|
||||
*/
|
||||
QDateTime nextSeventvActivity_;
|
||||
|
||||
QDateTime nextBttvActivity_;
|
||||
|
||||
/** The platform of the last live emote update ("7TV", "BTTV", "FFZ"). */
|
||||
QString lastLiveUpdateEmotePlatform_;
|
||||
/** The actor name of the last live emote update. */
|
||||
|
||||
Reference in New Issue
Block a user