2018-08-02 14:23:27 +02:00
|
|
|
#include "TwitchBadges.hpp"
|
|
|
|
|
2021-05-08 15:57:00 +02:00
|
|
|
#include <QBuffer>
|
|
|
|
#include <QIcon>
|
|
|
|
#include <QImageReader>
|
2018-08-02 14:23:27 +02:00
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QJsonValue>
|
|
|
|
#include <QThread>
|
2018-08-14 17:45:17 +02:00
|
|
|
|
2018-08-02 14:23:27 +02:00
|
|
|
#include "common/NetworkRequest.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "common/Outcome.hpp"
|
2021-05-03 00:08:08 +02:00
|
|
|
#include "common/QLogging.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "messages/Emote.hpp"
|
2018-08-02 14:23:27 +02:00
|
|
|
|
|
|
|
namespace chatterino {
|
|
|
|
|
2021-05-03 00:08:08 +02:00
|
|
|
TwitchBadges::TwitchBadges()
|
|
|
|
{
|
|
|
|
this->loadTwitchBadges();
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:23:27 +02:00
|
|
|
void TwitchBadges::loadTwitchBadges()
|
|
|
|
{
|
2021-05-03 00:08:08 +02:00
|
|
|
assert(this->loaded_ == false);
|
|
|
|
|
2018-08-06 21:17:03 +02:00
|
|
|
static QString url(
|
|
|
|
"https://badges.twitch.tv/v1/badges/global/display?language=en");
|
2018-08-02 14:23:27 +02:00
|
|
|
|
2019-08-20 21:50:36 +02:00
|
|
|
NetworkRequest(url)
|
|
|
|
.onSuccess([this](auto result) -> Outcome {
|
2018-10-21 13:43:02 +02:00
|
|
|
{
|
2021-05-03 00:08:08 +02:00
|
|
|
auto root = result.parseJson();
|
|
|
|
auto badgeSets = this->badgeSets_.access();
|
2018-08-06 21:17:03 +02:00
|
|
|
|
2021-05-03 00:08:08 +02:00
|
|
|
auto jsonSets = root.value("badge_sets").toObject();
|
|
|
|
for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt)
|
2019-08-20 21:50:36 +02:00
|
|
|
{
|
2021-05-03 00:08:08 +02:00
|
|
|
auto key = sIt.key();
|
|
|
|
auto versions =
|
|
|
|
sIt.value().toObject().value("versions").toObject();
|
|
|
|
|
|
|
|
for (auto vIt = versions.begin(); vIt != versions.end();
|
|
|
|
++vIt)
|
|
|
|
{
|
|
|
|
auto versionObj = vIt.value().toObject();
|
|
|
|
|
|
|
|
auto emote = Emote{
|
|
|
|
{""},
|
|
|
|
ImageSet{
|
|
|
|
Image::fromUrl({versionObj.value("image_url_1x")
|
|
|
|
.toString()},
|
|
|
|
1),
|
|
|
|
Image::fromUrl({versionObj.value("image_url_2x")
|
|
|
|
.toString()},
|
|
|
|
.5),
|
|
|
|
Image::fromUrl({versionObj.value("image_url_4x")
|
|
|
|
.toString()},
|
|
|
|
.25),
|
|
|
|
},
|
|
|
|
Tooltip{versionObj.value("title").toString()},
|
|
|
|
Url{versionObj.value("click_url").toString()}};
|
|
|
|
// "title"
|
|
|
|
// "clickAction"
|
|
|
|
|
|
|
|
(*badgeSets)[key][vIt.key()] =
|
|
|
|
std::make_shared<Emote>(emote);
|
|
|
|
}
|
2019-08-20 21:50:36 +02:00
|
|
|
}
|
|
|
|
}
|
2021-05-03 00:08:08 +02:00
|
|
|
this->loaded();
|
2019-08-20 21:50:36 +02:00
|
|
|
return Success;
|
|
|
|
})
|
2021-05-03 00:08:08 +02:00
|
|
|
.onError([this](auto res) {
|
|
|
|
qCDebug(chatterinoTwitch)
|
|
|
|
<< "Error loading Twitch Badges:" << res.status();
|
|
|
|
// Despite erroring out, we still want to reach the same point
|
|
|
|
// Loaded should still be set to true to not build up an endless queue, and the quuee should still be flushed.
|
|
|
|
this->loaded();
|
|
|
|
})
|
2019-08-20 21:50:36 +02:00
|
|
|
.execute();
|
2018-08-02 14:23:27 +02:00
|
|
|
}
|
|
|
|
|
2021-05-03 00:08:08 +02:00
|
|
|
void TwitchBadges::loaded()
|
|
|
|
{
|
|
|
|
std::unique_lock loadedLock(this->loadedMutex_);
|
|
|
|
|
|
|
|
assert(this->loaded_ == false);
|
|
|
|
|
|
|
|
this->loaded_ = true;
|
|
|
|
|
|
|
|
// Flush callback queue
|
|
|
|
std::unique_lock queueLock(this->queueMutex_);
|
2021-05-08 15:29:12 +02:00
|
|
|
|
|
|
|
// Once we have gained unique access of the queue, we can release our unique access of the loaded mutex allowing future calls to read locked_
|
|
|
|
loadedLock.unlock();
|
|
|
|
|
2021-05-03 00:08:08 +02:00
|
|
|
while (!this->callbackQueue_.empty())
|
|
|
|
{
|
|
|
|
auto callback = this->callbackQueue_.front();
|
|
|
|
this->callbackQueue_.pop();
|
|
|
|
this->getBadgeIcon(callback.first, callback.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-14 17:45:17 +02:00
|
|
|
boost::optional<EmotePtr> TwitchBadges::badge(const QString &set,
|
|
|
|
const QString &version) const
|
|
|
|
{
|
|
|
|
auto badgeSets = this->badgeSets_.access();
|
|
|
|
auto it = badgeSets->find(set);
|
2018-10-21 13:43:02 +02:00
|
|
|
if (it != badgeSets->end())
|
|
|
|
{
|
2018-08-14 17:45:17 +02:00
|
|
|
auto it2 = it->second.find(version);
|
2018-10-21 13:43:02 +02:00
|
|
|
if (it2 != it->second.end())
|
|
|
|
{
|
2018-08-14 17:45:17 +02:00
|
|
|
return it2->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
2021-05-03 00:08:08 +02:00
|
|
|
boost::optional<EmotePtr> TwitchBadges::badge(const QString &set) const
|
|
|
|
{
|
|
|
|
auto badgeSets = this->badgeSets_.access();
|
|
|
|
auto it = badgeSets->find(set);
|
|
|
|
if (it != badgeSets->end())
|
|
|
|
{
|
|
|
|
if (it->second.size() > 0)
|
|
|
|
{
|
|
|
|
return it->second.begin()->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchBadges::getBadgeIcon(const QString &name, BadgeIconCallback callback)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::shared_lock loadedLock(this->loadedMutex_);
|
|
|
|
|
|
|
|
if (!this->loaded_)
|
|
|
|
{
|
|
|
|
// Badges have not been loaded yet, store callback in a queue
|
|
|
|
std::unique_lock queueLock(this->queueMutex_);
|
|
|
|
this->callbackQueue_.push({name, std::move(callback)});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::shared_lock badgeLock(this->badgesMutex_);
|
|
|
|
if (this->badgesMap_.contains(name))
|
|
|
|
{
|
|
|
|
callback(name, this->badgesMap_[name]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Split string in format "name1/version1,name2/version2" to "name1", "version1"
|
|
|
|
// If not in list+version form, name will remain the same
|
|
|
|
auto targetBadge = name.split(",").at(0).split("/");
|
|
|
|
|
|
|
|
const auto badge = targetBadge.size() == 2
|
|
|
|
? this->badge(targetBadge.at(0), targetBadge.at(1))
|
|
|
|
: this->badge(targetBadge.at(0));
|
|
|
|
|
|
|
|
if (badge)
|
|
|
|
{
|
|
|
|
this->loadEmoteImage(name, (*badge)->images.getImage3(),
|
|
|
|
std::move(callback));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchBadges::getBadgeIcon(const DisplayBadge &badge,
|
|
|
|
BadgeIconCallback callback)
|
|
|
|
{
|
|
|
|
this->getBadgeIcon(badge.badgeName(), std::move(callback));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchBadges::getBadgeIcons(const QList<DisplayBadge> &badges,
|
|
|
|
BadgeIconCallback callback)
|
|
|
|
{
|
|
|
|
for (const auto &item : badges)
|
|
|
|
{
|
|
|
|
this->getBadgeIcon(item, callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchBadges::loadEmoteImage(const QString &name, ImagePtr image,
|
|
|
|
BadgeIconCallback &&callback)
|
|
|
|
{
|
|
|
|
NetworkRequest(image->url().string)
|
|
|
|
.concurrent()
|
|
|
|
.cache()
|
|
|
|
.onSuccess([this, name, callback](auto result) -> Outcome {
|
|
|
|
auto data = result.getData();
|
|
|
|
|
|
|
|
// const cast since we are only reading from it
|
|
|
|
QBuffer buffer(const_cast<QByteArray *>(&data));
|
|
|
|
buffer.open(QIODevice::ReadOnly);
|
|
|
|
QImageReader reader(&buffer);
|
|
|
|
|
|
|
|
QImage image;
|
|
|
|
if (reader.imageCount() == 0 || !reader.read(&image))
|
|
|
|
{
|
|
|
|
return Failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto icon = std::make_shared<QIcon>(QPixmap::fromImage(image));
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock lock(this->badgesMutex_);
|
|
|
|
this->badgesMap_[name] = icon;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(name, icon);
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
})
|
|
|
|
.execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
TwitchBadges *TwitchBadges::instance_;
|
|
|
|
|
|
|
|
TwitchBadges *TwitchBadges::instance()
|
|
|
|
{
|
|
|
|
if (TwitchBadges::instance_ == nullptr)
|
|
|
|
{
|
|
|
|
TwitchBadges::instance_ = new TwitchBadges();
|
|
|
|
}
|
|
|
|
|
|
|
|
return TwitchBadges::instance_;
|
|
|
|
}
|
|
|
|
|
2018-08-02 14:23:27 +02:00
|
|
|
} // namespace chatterino
|