diff --git a/chatterino.pro b/chatterino.pro index ff4a6dad1..4716dce9b 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -49,6 +49,7 @@ SOURCES += \ src/main.cpp \ src/application.cpp \ src/channel.cpp \ + src/channeldata.cpp \ src/colorscheme.cpp \ src/emojis.cpp \ src/ircmanager.cpp \ diff --git a/src/channeldata.cpp b/src/channeldata.cpp new file mode 100644 index 000000000..b95939d82 --- /dev/null +++ b/src/channeldata.cpp @@ -0,0 +1,5 @@ +#include "channeldata.hpp" + +namespace chatterino { + +} // namespace chatterino diff --git a/src/channeldata.hpp b/src/channeldata.hpp new file mode 100644 index 000000000..8f06f88d8 --- /dev/null +++ b/src/channeldata.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "lockedobject.hpp" +#include "messages/lazyloadedimage.hpp" + +namespace chatterino { + +class ChannelData +{ +public: + ChannelData() = default; + + LockedObject username; + LockedObject id; + +private: + // std::map subscriptionBadges; +}; + +}; // namespace chatterino diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp index 5dfe42de3..d5aa2dd06 100644 --- a/src/channelmanager.cpp +++ b/src/channelmanager.cpp @@ -120,4 +120,17 @@ void ChannelManager::removeChannel(const QString &channel) } } +const std::string &ChannelManager::getUserID(const std::string &username) +{ + auto it = this->usernameToID.find(username); + + /* + if (it != std::end(this->usernameToID)) { + return *it; + } + */ + + return "xd"; +} + } // namespace chatterino diff --git a/src/channelmanager.hpp b/src/channelmanager.hpp index 2b0e261bf..e31725718 100644 --- a/src/channelmanager.hpp +++ b/src/channelmanager.hpp @@ -1,6 +1,9 @@ #pragma once #include "channel.hpp" +#include "channeldata.hpp" + +#include namespace chatterino { @@ -24,11 +27,16 @@ public: std::shared_ptr getChannel(const QString &channel); void removeChannel(const QString &channel); + const std::string &getUserID(const std::string &username); + private: WindowManager &windowManager; EmoteManager &emoteManager; IrcManager &ircManager; + std::map usernameToID; + std::map channelDatas; + QMap, int>> _channels; QMutex _channelsMutex; diff --git a/src/concurrentmap.hpp b/src/concurrentmap.hpp index 18086e14a..4ba8f85da 100644 --- a/src/concurrentmap.hpp +++ b/src/concurrentmap.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace chatterino { @@ -64,4 +65,55 @@ private: QMap _map; }; +template +class ConcurrentStdMap +{ +public: + bool tryGet(const TKey &name, TValue &value) const + { + QMutexLocker lock(&_mutex); + + auto a = _map.find(name); + if (a == _map.end()) { + return false; + } + + value = a.value(); + + return true; + } + + TValue getOrAdd(const TKey &name, std::function addLambda) + { + QMutexLocker lock(&_mutex); + + auto a = _map.find(name); + if (a == _map.end()) { + TValue value = addLambda(); + _map.insert(name, value); + return value; + } + + return a.value(); + } + + void clear() + { + QMutexLocker lock(&_mutex); + + _map.clear(); + } + + void insert(const TKey &name, const TValue &value) + { + QMutexLocker lock(&_mutex); + + _map.insert(name, value); + } + +private: + mutable QMutex _mutex; + std::map _map; +}; + } // namespace chatterino diff --git a/src/lockedobject.hpp b/src/lockedobject.hpp new file mode 100644 index 000000000..edf57b404 --- /dev/null +++ b/src/lockedobject.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace chatterino { + +template +class LockedObject +{ +public: + LockedObject &operator=(const LockedObject &other) + { + this->mutex.lock(); + + this->data = other.getValue(); + + this->mutex.unlock(); + + return *this; + } + + LockedObject &operator=(const Type &other) + { + this->mutex.lock(); + + this->data = other; + + this->mutex.unlock(); + + return *this; + } + +private: + Type value; + std::mutex mutex; +}; + +} // namespace chatterino diff --git a/src/resources.cpp b/src/resources.cpp index be3c6d088..eb2c241a7 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -1,5 +1,6 @@ #include "resources.hpp" #include "emotemanager.hpp" +#include "util/urlfetch.hpp" #include "windowmanager.hpp" #include @@ -25,7 +26,7 @@ Resources::Resources(EmoteManager &emoteManager, WindowManager &windowManager) , badgeTurbo(lli(emoteManager, windowManager, ":/images/turbo_bg.png")) , badgeBroadcaster(lli(emoteManager, windowManager, ":/images/broadcaster_bg.png")) , badgePremium(lli(emoteManager, windowManager, ":/images/twitchprime_bg.png")) - , badgeVerified(lli(emoteManager, windowManager, ":/images/verified.png")) + , badgeVerified(lli(emoteManager, windowManager, ":/images/verified.png", 0.25)) , cheerBadge100000(lli(emoteManager, windowManager, ":/images/cheer100000")) , cheerBadge10000(lli(emoteManager, windowManager, ":/images/cheer10000")) , cheerBadge5000(lli(emoteManager, windowManager, ":/images/cheer5000")) @@ -35,6 +36,49 @@ Resources::Resources(EmoteManager &emoteManager, WindowManager &windowManager) , buttonBan(lli(emoteManager, windowManager, ":/images/button_ban.png", 0.25)) , buttonTimeout(lli(emoteManager, windowManager, ":/images/button_timeout.png", 0.25)) { + QString badgesUrl("https://badges.twitch.tv/v1/badges/global/display?language=en"); + + util::urlJsonFetch(badgesUrl, [this](QJsonObject &root) { + QJsonObject sets = root.value("badge_sets").toObject(); + + for (auto it = std::begin(sets); it != std::end(sets); ++it) { + printf("%s\n", qPrintable(it.key())); + + auto &badgeSet = this->badgeSets[it.key().toStdString()]; + + std::map &versionsMap = badgeSet.versions; + + QJsonObject versions = sets.value("versions").toObject(); + + for (auto versionIt = std::begin(versions); versionIt != std::end(versions); + ++versionIt) { + /* + std::string kkey = versionIt.key().toStdString(); + QJsonObject versionObj = versionIt.value().toObject(); + versionsMap.emplace(std::make_pair(kkey, versionObj)); + */ + } + } + }); +} + +Resources::BadgeVersion::BadgeVersion(QJsonObject &&root) +{ +} + +void Resources::Channel::loadData() +{ + /* + if (this->loaded) { + return; + } + + this->loaded = true; + + if (this->id.empty()) { + //util::urlJsonFetch() + } + */ } } // namespace chatterino diff --git a/src/resources.hpp b/src/resources.hpp index 8f34bb6e7..75ce869e6 100644 --- a/src/resources.hpp +++ b/src/resources.hpp @@ -2,6 +2,9 @@ #include "messages/lazyloadedimage.hpp" +#include +#include + namespace chatterino { class EmoteManager; @@ -28,8 +31,46 @@ public: messages::LazyLoadedImage *cheerBadge100; messages::LazyLoadedImage *cheerBadge1; + std::map cheerBadges; + + struct BadgeVersion { + BadgeVersion() = delete; + + explicit BadgeVersion(QJsonObject &&root); + + messages::LazyLoadedImage *badgeImage1x; + messages::LazyLoadedImage *badgeImage2x; + messages::LazyLoadedImage *badgeImage4x; + std::string description; + std::string title; + std::string clickAction; + std::string clickUrl; + }; + + struct BadgeSet { + std::map versions; + }; + + std::map badgeSets; + + bool bitBadgesLoaded = false; + bool dynamicBadgesLoaded = false; + messages::LazyLoadedImage *buttonBan; messages::LazyLoadedImage *buttonTimeout; + + struct Channel { + std::string id; + + std::mutex globalMapMutex; + + void loadData(); + + // std::atomic loaded = false; + }; + + // channelId + std::map channels; }; } // namespace chatterino diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index f024ed6a0..f3a72a7ab 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -18,9 +18,17 @@ TwitchMessageBuilder::TwitchMessageBuilder() { } -SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircMessage, - Channel *channel, const MessageParseArgs &args, - const Resources &resources, EmoteManager &emoteManager, +// When a twitch message is being parsed it makes sense for the builder to have access to: +// - The IRC Message +// - Message-specific parsing arguments (i.e. if ping sounds are disabled) +// - The Channel that the message originated from +// - The resources class for Badges, Moderation buttons +// - The Emote Manager for all different kinds of emotes +SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircMessage, // + Channel *channel, // + const MessageParseArgs &args, // + Resources &resources, // + EmoteManager &emoteManager, // WindowManager &windowManager) { TwitchMessageBuilder b; @@ -37,6 +45,13 @@ SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircM b.messageId = iterator.value().toString(); } + // room id + iterator = tags.find("room-id"); + std::string roomID; + if (iterator != std::end(tags)) { + roomID = iterator.value().toString().toStdString(); + } + // timestamps iterator = tags.find("tmi-sent-ts"); @@ -45,10 +60,12 @@ SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircM // badges iterator = tags.find("badges"); + const auto &channelResources = resources.channels[roomID]; + if (iterator != tags.end()) { auto badges = iterator.value().toString().split(','); - b.appendTwitchBadges(badges, resources, emoteManager); + b.appendTwitchBadges(badges, resources, emoteManager, channelResources); } // color @@ -331,8 +348,9 @@ void TwitchMessageBuilder::appendTwitchEmote( } } -void TwitchMessageBuilder::appendTwitchBadges(const QStringList &badges, const Resources &resources, - EmoteManager &emoteManager) +void TwitchMessageBuilder::appendTwitchBadges(const QStringList &badges, Resources &resources, + EmoteManager &emoteManager, + const Resources::Channel &channelResources) { for (QString badge : badges) { if (badge.isEmpty()) { @@ -340,9 +358,16 @@ void TwitchMessageBuilder::appendTwitchBadges(const QStringList &badges, const R } if (badge.startsWith("bits/")) { - long long int cheer = std::strtoll(badge.mid(5).toStdString().c_str(), nullptr, 10); - appendWord(Word(emoteManager.getCheerBadge(cheer), Word::BadgeCheer, QString(), - QString("Twitch Cheer" + QString::number(cheer)))); + if (!resources.bitBadgesLoaded) { + // Do nothing + continue; + } + + QString cheerAmountQS = badge.mid(5); + std::string cheerAmountString = cheerAmountQS.toStdString(); + + appendWord(Word(resources.cheerBadges[cheerAmountString], Word::BadgeCheer, QString(), + QString("Twitch Cheer" + cheerAmountQS))); } else if (badge == "staff/1") { appendWord( Word(resources.badgeStaff, Word::BadgeStaff, QString(), QString("Twitch Staff"))); @@ -382,7 +407,8 @@ void TwitchMessageBuilder::appendTwitchBadges(const QStringList &badges, const R // TODO: Implement subscriber badges here switch (index) { default: { - // printf("[TwitchMessageBuilder] Unhandled subscriber badge index: %d\n", index); + // printf("[TwitchMessageBuilder] Unhandled subscriber badge index: %d\n", + // index); } break; } } else { diff --git a/src/twitch/twitchmessagebuilder.hpp b/src/twitch/twitchmessagebuilder.hpp index 53938d48d..5bbbb9a26 100644 --- a/src/twitch/twitchmessagebuilder.hpp +++ b/src/twitch/twitchmessagebuilder.hpp @@ -2,13 +2,13 @@ #include "channel.hpp" #include "messages/messagebuilder.hpp" +#include "resources.hpp" #include #include namespace chatterino { -class Resources; class EmoteManager; class WindowManager; @@ -24,7 +24,7 @@ public: static messages::SharedMessage parse(const Communi::IrcPrivateMessage *ircMessage, Channel *channel, const messages::MessageParseArgs &args, - const Resources &resources, EmoteManager &emoteManager, + Resources &resources, EmoteManager &emoteManager, WindowManager &windowManager); // static bool sortTwitchEmotes( @@ -37,8 +37,8 @@ private: void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, std::vector> &vec, EmoteManager &emoteManager); - void appendTwitchBadges(const QStringList &badges, const Resources &resources, - EmoteManager &emoteManager); + void appendTwitchBadges(const QStringList &badges, Resources &resources, + EmoteManager &emoteManager, const Resources::Channel &channelResources); }; } // namespace twitch