mirror-chatterino2/src/emotemanager.cpp

437 lines
13 KiB
C++
Raw Normal View History

2017-06-11 09:31:45 +02:00
#include "emotemanager.hpp"
#include "resources.hpp"
#include "util/urlfetch.hpp"
#include "windowmanager.hpp"
2017-01-04 15:12:31 +01:00
2017-01-26 10:18:02 +01:00
#include <QDebug>
2017-01-26 17:26:20 +01:00
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
2017-01-26 17:26:20 +01:00
#include <memory>
2017-01-26 10:18:02 +01:00
2017-04-12 17:46:44 +02:00
#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}.0"
2017-04-14 17:52:22 +02:00
using namespace chatterino::messages;
2017-04-12 17:46:44 +02:00
2017-04-14 17:52:22 +02:00
namespace chatterino {
2017-01-18 21:30:23 +01:00
EmoteManager::EmoteManager(WindowManager &_windowManager, Resources &_resources)
: windowManager(_windowManager)
, resources(_resources)
{
// Note: Do not use this->resources in ctor
}
void EmoteManager::loadGlobalEmotes()
{
this->loadEmojis();
this->loadBTTVEmotes();
this->loadFFZEmotes();
}
2017-04-12 17:46:44 +02:00
void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
BTTVEmoteMap &channelEmoteMap)
2017-04-12 17:46:44 +02:00
{
printf("[EmoteManager] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName));
QString url("https://api.betterttv.net/2/channels/" + channelName);
util::urlFetchJSON(url, [this, &channelEmoteMap](QJsonObject &rootNode) {
channelEmoteMap.clear();
auto emotesNode = rootNode.value("emotes").toArray();
QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString();
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
QString id = emoteObject.value("id").toString();
QString code = emoteObject.value("code").toString();
// emoteObject.value("imageType").toString();
QString link = linkTemplate;
link.detach();
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] {
return new LazyLoadedImage(*this, this->windowManager, link, 1, code,
code + "\nChannel BTTV Emote");
});
this->bttvChannelEmotes.insert(code, emote);
channelEmoteMap.insert(code, emote);
}
});
}
void EmoteManager::reloadFFZChannelEmotes(
const QString &channelName,
ConcurrentMap<QString, messages::LazyLoadedImage *> &channelEmoteMap)
{
printf("[EmoteManager] Reload FFZ Channel Emotes for channel %s\n", qPrintable(channelName));
QString url("http://api.frankerfacez.com/v1/room/" + channelName);
util::urlFetchJSON(url, [this, &channelEmoteMap](QJsonObject &rootNode) {
channelEmoteMap.clear();
auto setsNode = rootNode.value("sets").toObject();
for (const QJsonValue &setNode : setsNode) {
auto emotesNode = setNode.toObject().value("emoticons").toArray();
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
// margins
int id = emoteObject.value("id").toInt();
QString code = emoteObject.value("name").toString();
QJsonObject urls = emoteObject.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
auto emote =
this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &url1] {
return new LazyLoadedImage(*this, this->windowManager, url1, 1, code,
code + "\nGlobal FFZ Emote");
});
this->ffzChannelEmotes.insert(code, emote);
channelEmoteMap.insert(code, emote);
}
}
});
2017-04-12 17:46:44 +02:00
}
2017-01-04 15:12:31 +01:00
2017-04-12 17:46:44 +02:00
ConcurrentMap<QString, twitch::EmoteValue *> &EmoteManager::getTwitchEmotes()
{
return _twitchEmotes;
}
2017-01-05 20:49:33 +01:00
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getBTTVEmotes()
2017-04-12 17:46:44 +02:00
{
return _bttvEmotes;
}
2017-02-06 17:42:28 +01:00
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getFFZEmotes()
2017-04-12 17:46:44 +02:00
{
return _ffzEmotes;
}
2017-01-15 16:38:30 +01:00
2017-04-12 17:46:44 +02:00
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getChatterinoEmotes()
2017-01-05 16:07:20 +01:00
{
2017-04-12 17:46:44 +02:00
return _chatterinoEmotes;
2017-01-05 16:07:20 +01:00
}
2017-01-04 15:12:31 +01:00
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getBTTVChannelEmoteFromCaches()
2017-04-12 17:46:44 +02:00
{
return _bttvChannelEmoteFromCaches;
}
ConcurrentMap<int, messages::LazyLoadedImage *> &EmoteManager::getFFZChannelEmoteFromCaches()
2017-04-12 17:46:44 +02:00
{
return _ffzChannelEmoteFromCaches;
}
ConcurrentMap<long, messages::LazyLoadedImage *> &EmoteManager::getTwitchEmoteFromCache()
{
return _twitchEmoteFromCache;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getMiscImageFromCache()
{
return _miscImageFromCache;
}
void EmoteManager::loadEmojis()
{
QFile file(":/emojidata.txt");
file.open(QFile::ReadOnly);
QTextStream in(&file);
uint unicodeBytes[4];
while (!in.atEnd()) {
// Line example: sunglasses 1f60e
QString line = in.readLine();
if (line.at(0) == '#') {
// Ignore lines starting with # (comments)
continue;
}
QStringList parts = line.split(' ');
if (parts.length() < 2) {
continue;
}
QString shortCode = parts[0];
QString code = parts[1];
QStringList unicodeCharacters = code.split('-');
if (unicodeCharacters.length() < 1) {
continue;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] = QString(unicodeCharacter).toUInt(nullptr, 16);
}
EmojiData emojiData{
QString::fromUcs4(unicodeBytes, numUnicodeBytes), //
code, //
2017-07-02 17:37:17 +02:00
shortCode, //
};
2017-07-02 17:37:17 +02:00
this->emojiShortCodeToEmoji.insert(shortCode, emojiData);
2017-07-02 17:37:17 +02:00
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
}
}
void EmoteManager::parseEmojis(
2017-07-02 17:37:17 +02:00
std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &parsedWords, const QString &text)
{
2017-07-02 17:37:17 +02:00
int lastParsedEmojiEndIndex = 0;
for (auto i = 0; i < text.length() - 1; i++) {
2017-07-02 17:37:17 +02:00
const QChar character = text.at(i);
if (character.isLowSurrogate()) {
continue;
}
auto it = this->emojiFirstByte.find(character);
if (it == this->emojiFirstByte.end()) {
// No emoji starts with this character
continue;
}
const QVector<EmojiData> possibleEmojis = it.value();
int remainingCharacters = text.length() - i;
EmojiData matchedEmoji;
int matchedEmojiLength = 0;
for (const EmojiData &emoji : possibleEmojis) {
if (remainingCharacters < emoji.value.length()) {
// It cannot be this emoji, there's not enough space for it
continue;
}
bool match = true;
for (int j = 1; j < emoji.value.length(); ++j) {
if (text.at(i + j) != emoji.value.at(j)) {
match = false;
break;
}
}
2017-07-02 17:37:17 +02:00
if (match) {
matchedEmoji = emoji;
matchedEmojiLength = emoji.value.length();
break;
}
}
2017-07-02 17:37:17 +02:00
if (matchedEmojiLength == 0) {
continue;
}
int currentParsedEmojiFirstIndex = i;
int currentParsedEmojiEndIndex = i + (matchedEmojiLength);
int charactersFromLastParsedEmoji = currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
if (charactersFromLastParsedEmoji > 0) {
// Add characters inbetween emojis
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji)));
}
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
matchedEmoji.code + ".png";
// Create or fetch cached emoji image
auto emojiImage = this->emojiCache.getOrAdd(url, [this, &url] {
return new LazyLoadedImage(*this, this->windowManager, url, 0.35); //
});
// Push the emoji as a word to parsedWords
parsedWords.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(emojiImage, QString()));
lastParsedEmojiEndIndex = currentParsedEmojiEndIndex;
i += matchedEmojiLength - 1;
}
2017-07-02 17:37:17 +02:00
if (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters
parsedWords.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
nullptr, text.mid(lastParsedEmojiEndIndex)));
}
}
void EmoteManager::loadBTTVEmotes()
2017-01-26 17:26:20 +01:00
{
// bttv
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.betterttv.net/2/emotes");
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
auto emotes = root.value("emotes").toArray();
2017-04-12 17:46:44 +02:00
QString linkTemplate = "https:" + root.value("urlTemplate").toString();
2017-01-26 17:26:20 +01:00
for (const QJsonValue &emote : emotes) {
QString id = emote.toObject().value("id").toString();
QString code = emote.toObject().value("code").toString();
// emote.value("imageType").toString();
2017-04-12 17:46:44 +02:00
QString tmp = linkTemplate;
2017-01-26 17:26:20 +01:00
tmp.detach();
2017-04-12 17:46:44 +02:00
QString url = tmp.replace("{{id}}", id).replace("{{image}}", "1x");
2017-01-26 17:26:20 +01:00
EmoteManager::getBTTVEmotes().insert(
code, new LazyLoadedImage(*this, this->windowManager, url, 1, code,
code + "\nGlobal BTTV Emote"));
2017-01-26 17:26:20 +01:00
}
}
reply->deleteLater();
manager->deleteLater();
});
}
void EmoteManager::loadFFZEmotes()
2017-01-26 17:26:20 +01:00
{
2017-02-09 00:03:46 +01:00
// ffz
2017-01-26 17:26:20 +01:00
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.frankerfacez.com/v1/set/global");
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
auto sets = root.value("sets").toObject();
for (const QJsonValue &set : sets) {
auto emoticons = set.toObject().value("emoticons").toArray();
for (const QJsonValue &emote : emoticons) {
QJsonObject object = emote.toObject();
// margins
2017-04-12 17:46:44 +02:00
// int id = object.value("id").toInt();
2017-01-26 17:26:20 +01:00
QString code = object.value("name").toString();
QJsonObject urls = object.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
EmoteManager::getBTTVEmotes().insert(
code, new LazyLoadedImage(*this, this->windowManager, url1, 1, code,
code + "\nGlobal FFZ Emote"));
2017-01-26 17:26:20 +01:00
}
}
}
reply->deleteLater();
manager->deleteLater();
});
}
2017-04-12 17:46:44 +02:00
LazyLoadedImage *EmoteManager::getTwitchEmoteById(const QString &name, long id)
2017-01-06 23:28:48 +01:00
{
return EmoteManager::_twitchEmoteFromCache.getOrAdd(id, [this, &name, &id] {
2017-04-12 17:46:44 +02:00
qDebug() << "added twitch emote: " << id;
2017-01-13 18:59:11 +01:00
qreal scale;
QString url = getTwitchEmoteLink(id, scale);
return new LazyLoadedImage(*this, this->windowManager, url, scale, name,
name + "\nTwitch Emote");
2017-01-13 18:59:11 +01:00
});
}
2017-04-12 17:46:44 +02:00
QString EmoteManager::getTwitchEmoteLink(long id, qreal &scale)
2017-01-13 18:59:11 +01:00
{
scale = .5;
2017-04-12 17:46:44 +02:00
QString value = TWITCH_EMOTE_TEMPLATE;
2017-01-26 10:18:02 +01:00
value.detach();
return value.replace("{id}", QString::number(id)).replace("{scale}", "2");
2017-01-06 23:28:48 +01:00
}
2017-04-12 17:46:44 +02:00
LazyLoadedImage *EmoteManager::getCheerImage(long long amount, bool animated)
2017-01-05 16:07:20 +01:00
{
// TODO: fix this xD
return this->getCheerBadge(amount);
2017-01-05 20:49:33 +01:00
}
2017-01-04 15:12:31 +01:00
2017-04-12 17:46:44 +02:00
LazyLoadedImage *EmoteManager::getCheerBadge(long long amount)
2017-01-05 20:49:33 +01:00
{
2017-01-11 18:52:09 +01:00
if (amount >= 100000) {
return this->resources.cheerBadge100000;
2017-01-11 18:52:09 +01:00
} else if (amount >= 10000) {
return this->resources.cheerBadge10000;
2017-01-11 18:52:09 +01:00
} else if (amount >= 5000) {
return this->resources.cheerBadge5000;
2017-01-11 18:52:09 +01:00
} else if (amount >= 1000) {
return this->resources.cheerBadge1000;
2017-01-11 18:52:09 +01:00
} else if (amount >= 100) {
return this->resources.cheerBadge100;
2017-01-11 18:52:09 +01:00
} else {
return this->resources.cheerBadge1;
}
}
boost::signals2::signal<void()> &EmoteManager::getGifUpdateSignal()
{
if (!_gifUpdateTimerInitiated) {
_gifUpdateTimerInitiated = true;
_gifUpdateTimer.setInterval(30);
_gifUpdateTimer.start();
QObject::connect(&_gifUpdateTimer, &QTimer::timeout, [this] {
_gifUpdateTimerSignal();
this->windowManager.repaintGifEmotes();
});
2017-01-05 20:49:33 +01:00
}
return _gifUpdateTimerSignal;
2017-01-04 15:12:31 +01:00
}
2017-05-27 16:16:39 +02:00
} // namespace chatterino