2017-06-11 09:31:45 +02:00
|
|
|
#include "emotemanager.hpp"
|
|
|
|
#include "resources.hpp"
|
2017-06-13 21:13:58 +02:00
|
|
|
#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-06-07 10:09:24 +02:00
|
|
|
|
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
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
EmoteManager::EmoteManager(WindowManager &_windowManager, Resources &_resources)
|
|
|
|
: windowManager(_windowManager)
|
|
|
|
, resources(_resources)
|
|
|
|
{
|
|
|
|
// Note: Do not use this->resources in ctor
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmoteManager::loadGlobalEmotes()
|
|
|
|
{
|
2017-06-26 16:41:20 +02:00
|
|
|
this->loadEmojis();
|
2017-06-13 21:13:58 +02:00
|
|
|
this->loadBTTVEmotes();
|
|
|
|
this->loadFFZEmotes();
|
|
|
|
}
|
2017-04-12 17:46:44 +02:00
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
|
|
|
|
BTTVEmoteMap &channelEmoteMap)
|
2017-04-12 17:46:44 +02:00
|
|
|
{
|
2017-06-13 21:13:58 +02:00
|
|
|
printf("[EmoteManager] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName));
|
|
|
|
|
|
|
|
QString url("https://api.betterttv.net/2/channels/" + channelName);
|
2017-06-26 16:41:20 +02:00
|
|
|
util::urlFetchJSON(url, [this, &channelEmoteMap](QJsonObject &rootNode) {
|
2017-06-13 21:13:58 +02:00
|
|
|
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);
|
|
|
|
|
2017-06-26 16:41:20 +02:00
|
|
|
util::urlFetchJSON(url, [this, &channelEmoteMap](QJsonObject &rootNode) {
|
2017-06-13 21:13:58 +02:00
|
|
|
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
|
|
|
|
2017-06-13 21:13:58 +02: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
|
|
|
|
2017-06-13 21:13:58 +02: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
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getBTTVChannelEmoteFromCaches()
|
2017-04-12 17:46:44 +02:00
|
|
|
{
|
|
|
|
return _bttvChannelEmoteFromCaches;
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:41:20 +02:00
|
|
|
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, //
|
|
|
|
};
|
|
|
|
|
|
|
|
shortCodeToEmoji.insert(shortCode, emojiData);
|
|
|
|
emojiToShortCode.insert(emojiData.value, shortCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
for (auto const &emoji : shortCodeToEmoji.toStdMap()) {
|
|
|
|
auto iter = firstEmojiChars.find(emoji.first.at(0));
|
|
|
|
|
|
|
|
if (iter != firstEmojiChars.end()) {
|
|
|
|
iter.value().insert(emoji.second.value, emoji.second.value);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
firstEmojiChars.insert(emoji.first.at(0),
|
|
|
|
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmoteManager::parseEmojis(
|
|
|
|
std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector, const QString &text)
|
|
|
|
{
|
|
|
|
// TODO(pajlada): Add this method to EmoteManager instead
|
|
|
|
long lastSlice = 0;
|
|
|
|
|
|
|
|
for (auto i = 0; i < text.length() - 1; i++) {
|
|
|
|
if (!text.at(i).isLowSurrogate()) {
|
|
|
|
auto iter = firstEmojiChars.find(text.at(i));
|
|
|
|
|
|
|
|
if (iter != firstEmojiChars.end()) {
|
|
|
|
for (auto j = std::min(8, text.length() - i); j > 0; j--) {
|
|
|
|
QString emojiString = text.mid(i, 2);
|
|
|
|
auto emojiIter = iter.value().find(emojiString);
|
|
|
|
|
|
|
|
if (emojiIter != iter.value().end()) {
|
|
|
|
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
|
|
|
|
"emojione/2.2.6/assets/png/" +
|
|
|
|
emojiIter.value() + ".png";
|
|
|
|
|
|
|
|
if (i - lastSlice != 0) {
|
|
|
|
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
|
|
|
|
nullptr, text.mid(lastSlice, i - lastSlice)));
|
|
|
|
}
|
|
|
|
|
|
|
|
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
|
|
|
|
emojis.getOrAdd(url,
|
|
|
|
[this, &url] {
|
|
|
|
return new LazyLoadedImage(
|
|
|
|
*this, this->windowManager, url, 0.35); //
|
|
|
|
}),
|
|
|
|
QString()));
|
|
|
|
|
|
|
|
i += j - 1;
|
|
|
|
|
|
|
|
lastSlice = i + 1;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastSlice < text.length()) {
|
|
|
|
vector.push_back(
|
|
|
|
std::tuple<messages::LazyLoadedImage *, QString>(nullptr, text.mid(lastSlice)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
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
|
|
|
|
2017-06-13 21:13:58 +02: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();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
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();
|
|
|
|
|
2017-06-13 21:13:58 +02:00
|
|
|
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
|
|
|
{
|
2017-06-13 21:13:58 +02: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);
|
2017-06-13 21:13:58 +02:00
|
|
|
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
|
|
|
{
|
2017-01-15 17:21:28 +01:00
|
|
|
// TODO: fix this xD
|
2017-06-13 21:13:58 +02:00
|
|
|
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) {
|
2017-06-13 21:13:58 +02:00
|
|
|
return this->resources.cheerBadge100000;
|
2017-01-11 18:52:09 +01:00
|
|
|
} else if (amount >= 10000) {
|
2017-06-13 21:13:58 +02:00
|
|
|
return this->resources.cheerBadge10000;
|
2017-01-11 18:52:09 +01:00
|
|
|
} else if (amount >= 5000) {
|
2017-06-13 21:13:58 +02:00
|
|
|
return this->resources.cheerBadge5000;
|
2017-01-11 18:52:09 +01:00
|
|
|
} else if (amount >= 1000) {
|
2017-06-13 21:13:58 +02:00
|
|
|
return this->resources.cheerBadge1000;
|
2017-01-11 18:52:09 +01:00
|
|
|
} else if (amount >= 100) {
|
2017-06-13 21:13:58 +02:00
|
|
|
return this->resources.cheerBadge100;
|
2017-01-11 18:52:09 +01:00
|
|
|
} else {
|
2017-06-13 21:13:58 +02:00
|
|
|
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
|
|
|
}
|
2017-06-13 21:13:58 +02:00
|
|
|
|
|
|
|
return _gifUpdateTimerSignal;
|
2017-01-04 15:12:31 +01:00
|
|
|
}
|
2017-05-27 16:16:39 +02:00
|
|
|
|
|
|
|
} // namespace chatterino
|