mirror-chatterino2/src/singletons/emotemanager.cpp

460 lines
13 KiB
C++
Raw Normal View History

2017-06-11 09:31:45 +02:00
#include "emotemanager.hpp"
#include "application.hpp"
#include "common.hpp"
2017-12-31 00:50:07 +01:00
#include "singletons/settingsmanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/urlfetch.hpp"
2017-01-04 15:12:31 +01:00
2017-01-26 10:18:02 +01:00
#include <QDebug>
2017-12-31 00:50:07 +01:00
#include <QFile>
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
2018-02-05 15:11:50 +01:00
using namespace chatterino::providers::twitch;
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-12-31 22:58:35 +01:00
namespace singletons {
2017-01-18 21:30:23 +01:00
namespace {
QString GetFFZEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{
auto emote = urls.value(emoteScale);
if (emote.isUndefined()) {
return "";
}
assert(emote.isString());
return "https:" + emote.toString();
}
void FillInFFZEmoteData(const QJsonObject &urls, const QString &code, const QString &tooltip,
util::EmoteData &emoteData)
{
QString url1x = GetFFZEmoteLink(urls, "1");
QString url2x = GetFFZEmoteLink(urls, "2");
QString url3x = GetFFZEmoteLink(urls, "4");
assert(!url1x.isEmpty());
emoteData.image1x = new Image(url1x, 1, code, tooltip);
if (!url2x.isEmpty()) {
emoteData.image2x = new Image(url2x, 0.5, code, tooltip);
}
if (!url3x.isEmpty()) {
emoteData.image3x = new Image(url3x, 0.25, code, tooltip);
}
}
} // namespace
2018-04-01 16:43:30 +02:00
EmoteManager::EmoteManager()
: findShortCodesRegex(":([-+\\w]+):")
{
qDebug() << "init EmoteManager";
}
void EmoteManager::initialize()
{
2018-05-26 20:26:25 +02:00
getApp()->accounts->twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->twitch.getCurrent();
assert(currentUser);
this->twitch.refresh(currentUser);
});
this->loadEmojis();
2018-06-05 17:39:49 +02:00
this->bttv.loadGlobalEmotes();
this->loadFFZEmotes();
}
2017-04-12 17:46:44 +02:00
2017-12-31 22:58:35 +01:00
void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
std::weak_ptr<util::EmoteMap> _map)
{
printf("[EmoteManager] Reload FFZ Channel Emotes for channel %s\n", qPrintable(channelName));
2018-04-14 15:07:20 +02:00
QString url("https://api.frankerfacez.com/v1/room/" + channelName);
2017-10-27 22:04:05 +02:00
util::NetworkRequest req(url);
req.setCaller(QThread::currentThread());
req.setTimeout(3000);
req.getJSON([this, channelName, _map](QJsonObject &rootNode) {
auto map = _map.lock();
2017-10-27 22:04:05 +02:00
if (_map.expired()) {
return;
}
2017-10-27 22:04:05 +02:00
map->clear();
2017-10-27 22:04:05 +02:00
auto setsNode = rootNode.value("sets").toObject();
2017-10-27 22:04:05 +02:00
std::vector<std::string> codes;
for (const QJsonValue &setNode : setsNode) {
auto emotesNode = setNode.toObject().value("emoticons").toArray();
2017-10-27 22:04:05 +02:00
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
2017-10-27 22:04:05 +02:00
// margins
int id = emoteObject.value("id").toInt();
QString code = emoteObject.value("name").toString();
2017-10-27 22:04:05 +02:00
QJsonObject urls = emoteObject.value("urls").toObject();
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [id, &code, &urls] {
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
return emoteData;
});
2017-10-27 22:04:05 +02:00
this->ffzChannelEmotes.insert(code, emote);
map->insert(code, emote);
codes.push_back(code.toStdString());
}
2017-10-27 22:04:05 +02:00
this->ffzChannelEmoteCodes[channelName.toStdString()] = codes;
}
});
2017-04-12 17:46:44 +02:00
}
2017-01-04 15:12:31 +01:00
2017-12-31 22:58:35 +01:00
util::EmoteMap &EmoteManager::getFFZEmotes()
2017-04-12 17:46:44 +02:00
{
2017-07-23 09:53:50 +02:00
return ffzGlobalEmotes;
2017-04-12 17:46:44 +02:00
}
2017-01-15 16:38:30 +01:00
2017-12-31 22:58:35 +01:00
util::EmoteMap &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
util::EmojiMap &EmoteManager::getEmojis()
2017-12-19 03:41:31 +01:00
{
return this->emojis;
}
2017-12-31 22:58:35 +01:00
util::ConcurrentMap<int, util::EmoteData> &EmoteManager::getFFZChannelEmoteFromCaches()
2017-04-12 17:46:44 +02:00
{
return _ffzChannelEmoteFromCaches;
}
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);
}
QString unicodeString = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
code + ".png";
EmojiData emojiData{
unicodeString, //
code, //
shortCode, //
{new Image(url, 0.35, unicodeString, ":" + shortCode + ":<br/>Emoji")},
};
2017-07-02 17:37:17 +02:00
this->emojiShortCodeToEmoji.insert(shortCode, emojiData);
this->emojiShortCodes.push_back(shortCode.toStdString());
2017-07-02 17:37:17 +02:00
this->emojiFirstByte[emojiData.value.at(0)].append(emojiData);
2017-07-02 18:12:11 +02:00
this->emojis.insert(code, emojiData);
}
for (auto &p : this->emojiFirstByte) {
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
return lhs.value.length() > rhs.value.length();
});
}
}
2017-12-31 22:58:35 +01:00
void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>> &parsedWords,
const QString &text)
{
2017-07-02 17:37:17 +02:00
int lastParsedEmojiEndIndex = 0;
for (auto i = 0; i < text.length(); ++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 - 1;
2017-07-02 17:37:17 +02:00
EmojiData matchedEmoji;
int matchedEmojiLength = 0;
for (const EmojiData &emoji : possibleEmojis) {
int emojiExtraCharacters = emoji.value.length() - 1;
if (emojiExtraCharacters > remainingCharacters) {
2017-07-02 17:37:17 +02:00
// 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.emplace_back(util::EmoteData(), text.mid(lastParsedEmojiEndIndex,
charactersFromLastParsedEmoji));
2017-07-02 17:37:17 +02:00
}
// Push the emoji as a word to parsedWords
parsedWords.push_back(
std::tuple<util::EmoteData, QString>(matchedEmoji.emoteData, QString()));
2017-07-02 17:37:17 +02:00
lastParsedEmojiEndIndex = currentParsedEmojiEndIndex;
i += matchedEmojiLength - 1;
}
2017-07-02 17:37:17 +02:00
if (lastParsedEmojiEndIndex < text.length()) {
// Add remaining characters
parsedWords.emplace_back(util::EmoteData(), text.mid(lastParsedEmojiEndIndex));
}
}
QString EmoteManager::replaceShortCodes(const QString &text)
{
QString ret(text);
auto it = this->findShortCodesRegex.globalMatch(text);
int32_t offset = 0;
while (it.hasNext()) {
auto match = it.next();
auto capturedString = match.captured();
QString matchString = capturedString.toLower().mid(1, capturedString.size() - 2);
auto emojiIt = this->emojiShortCodeToEmoji.constFind(matchString);
if (emojiIt == this->emojiShortCodeToEmoji.constEnd()) {
continue;
}
auto emojiData = emojiIt.value();
ret.replace(offset + match.capturedStart(), match.capturedLength(), emojiData.value);
offset += emojiData.value.size() - match.capturedLength();
}
return ret;
}
void EmoteManager::loadFFZEmotes()
2017-01-26 17:26:20 +01:00
{
QString url("https://api.frankerfacez.com/v1/set/global");
2017-01-26 17:26:20 +01:00
util::NetworkRequest req(url);
req.setCaller(QThread::currentThread());
req.setTimeout(30000);
req.getJSON([this](QJsonObject &root) {
auto sets = root.value("sets").toObject();
2017-01-26 17:26:20 +01:00
std::vector<std::string> codes;
for (const QJsonValue &set : sets) {
auto emoticons = set.toObject().value("emoticons").toArray();
2017-01-26 17:26:20 +01:00
for (const QJsonValue &emote : emoticons) {
QJsonObject object = emote.toObject();
2017-01-26 17:26:20 +01:00
QString code = object.value("name").toString();
int id = object.value("id").toInt();
QJsonObject urls = object.value("urls").toObject();
util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
this->ffzGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString());
2017-01-26 17:26:20 +01:00
}
this->ffzGlobalEmoteCodes = codes;
}
2017-01-26 17:26:20 +01:00
});
}
2017-01-26 17:26:20 +01:00
2017-12-31 22:58:35 +01:00
util::EmoteData EmoteManager::getCheerImage(long long amount, bool animated)
2017-01-05 16:07:20 +01:00
{
// TODO: fix this xD
2017-12-31 22:58:35 +01:00
return util::EmoteData();
}
pajlada::Signals::NoArgSignal &EmoteManager::getGifUpdateSignal()
{
if (!this->gifUpdateTimerInitiated) {
auto app = getApp();
this->gifUpdateTimerInitiated = true;
this->gifUpdateTimer.setInterval(30);
this->gifUpdateTimer.start();
app->settings->enableGifAnimations.connect([this](bool enabled, auto) {
if (enabled) {
this->gifUpdateTimer.start();
} else {
this->gifUpdateTimer.stop();
}
});
QObject::connect(&this->gifUpdateTimer, &QTimer::timeout, [this] {
this->gifUpdateTimerSignal.invoke();
2017-12-31 00:50:07 +01:00
// fourtf:
auto app = getApp();
app->windows->repaintGifEmotes();
});
2017-01-05 20:49:33 +01:00
}
return this->gifUpdateTimerSignal;
2017-01-04 15:12:31 +01:00
}
2017-05-27 16:16:39 +02:00
} // namespace singletons
2017-05-27 16:16:39 +02:00
} // namespace chatterino
#if 0
namespace chatterino {
void EmojiTest()
{
auto &emoteManager = singletons::EmoteManager::getInstance();
emoteManager.loadEmojis();
{
std::vector<std::tuple<util::EmoteData, QString>> dummy;
// couple_mm 1f468-2764-1f468
// "\154075\156150❤\154075\156150"
// [0] 55357 0xd83d QChar
// [1] 56424 0xdc68 QChar
// [2] '❤' 10084 0x2764 QChar
// [3] 55357 0xd83d QChar
// [4] 56424 0xdc68 QChar
QString text = "👨❤👨";
emoteManager.parseEmojis(dummy, text);
assert(dummy.size() == 1);
}
{
std::vector<std::tuple<util::EmoteData, QString>> dummy;
// "✍\154074\157777"
// [0] '✍' 9997 0x270d QChar
// [1] 55356 0xd83c QChar
// [2] 57343 0xdfff QChar
QString text = "✍🏿";
emoteManager.parseEmojis(dummy, text);
assert(dummy.size() == 1);
assert(std::get<0>(dummy[0]).isValid());
}
{
std::vector<std::tuple<util::EmoteData, QString>> dummy;
QString text = "";
emoteManager.parseEmojis(dummy, text);
assert(dummy.size() == 1);
assert(std::get<0>(dummy[0]).isValid());
}
}
} // namespace chatterino
#endif