diff --git a/src/accountmanager.cpp b/src/accountmanager.cpp index 4dcfdd67c..c96831422 100644 --- a/src/accountmanager.cpp +++ b/src/accountmanager.cpp @@ -1,5 +1,7 @@ #include "accountmanager.hpp" +#include + namespace chatterino { namespace { @@ -25,6 +27,9 @@ AccountManager::AccountManager() if (!envUsername.isEmpty() && !envOauthToken.isEmpty()) { this->addTwitchUser(twitch::TwitchUser(envUsername, envOauthToken, "")); } + + pajlada::Settings::Setting::set( + "/accounts/current/roomID", "11148817", pajlada::Settings::SettingOption::DoNotWriteToJSON); } twitch::TwitchUser &AccountManager::getTwitchAnon() diff --git a/src/application.cpp b/src/application.cpp index 7d4d8bce7..254a006db 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -9,7 +9,8 @@ namespace chatterino { // It will create the instances of the major classes, and connect their signals to each other Application::Application() - : windowManager(this->channelManager, this->colorScheme) + : completionManager(this->emoteManager) + , windowManager(this->channelManager, this->colorScheme, this->completionManager) , colorScheme(this->windowManager) , emoteManager(this->windowManager, this->resources) , resources(this->emoteManager, this->windowManager) diff --git a/src/completionmanager.cpp b/src/completionmanager.cpp index ff33f202c..e040adad1 100644 --- a/src/completionmanager.cpp +++ b/src/completionmanager.cpp @@ -1,8 +1,11 @@ #include "completionmanager.hpp" +#include "common.hpp" +#include "emotemanager.hpp" namespace chatterino { -CompletionManager::CompletionManager() +CompletionManager::CompletionManager(EmoteManager &_emoteManager) + : emoteManager(_emoteManager) { } @@ -10,11 +13,58 @@ CompletionModel *CompletionManager::createModel(const std::string &channelName) { CompletionModel *ret = new CompletionModel(); + this->updateModel(ret, channelName); + + this->emoteManager.bttvGlobalEmoteCodes.updated.connect([=]() { + this->updateModel(ret, channelName); // + }); + + this->emoteManager.ffzGlobalEmoteCodes.updated.connect([=]() { + this->updateModel(ret, channelName); // + }); + + this->emoteManager.bttvChannelEmoteCodes[channelName].updated.connect([=]() { + this->updateModel(ret, channelName); // + }); + + this->emoteManager.ffzChannelEmoteCodes[channelName].updated.connect([=]() { + this->updateModel(ret, channelName); // + }); + return ret; } void CompletionManager::updateModel(CompletionModel *model, const std::string &channelName) { + model->emotes.clear(); + + for (const auto &m : this->emoteManager.twitchAccountEmotes) { + for (const auto &emoteName : m.second.emoteCodes) { + model->emotes.push_back(qS(emoteName)); + } + } + + std::vector &bttvGlobalEmoteCodes = this->emoteManager.bttvGlobalEmoteCodes; + for (const auto &m : bttvGlobalEmoteCodes) { + model->emotes.push_back(qS(m)); + } + + std::vector &ffzGlobalEmoteCodes = this->emoteManager.ffzGlobalEmoteCodes; + for (const auto &m : ffzGlobalEmoteCodes) { + model->emotes.push_back(qS(m)); + } + + std::vector &bttvChannelEmoteCodes = + this->emoteManager.bttvChannelEmoteCodes[channelName]; + for (const auto &m : bttvChannelEmoteCodes) { + model->emotes.push_back(qS(m)); + } + + std::vector &ffzChannelEmoteCodes = + this->emoteManager.ffzChannelEmoteCodes[channelName]; + for (const auto &m : ffzChannelEmoteCodes) { + model->emotes.push_back(qS(m)); + } } } // namespace chatterino diff --git a/src/completionmanager.hpp b/src/completionmanager.hpp index 191a4c670..29980ac00 100644 --- a/src/completionmanager.hpp +++ b/src/completionmanager.hpp @@ -1,12 +1,15 @@ #pragma once -#include +#include +#include #include namespace chatterino { -class CompletionModel : public QAbstractItemModel +class EmoteManager; + +class CompletionModel : public QAbstractListModel { public: virtual int columnCount(const QModelIndex & /*parent*/) const override @@ -16,34 +19,27 @@ public: virtual QVariant data(const QModelIndex &index, int role) const override { - // TODO: Implement - return QVariant(); - } - - virtual QModelIndex index(int row, int column, const QModelIndex &parent) const override - { - // TODO: Implement - return QModelIndex(); - } - - virtual QModelIndex parent(const QModelIndex &child) const override - { - return QModelIndex(); + // TODO: Implement more safely + return QVariant(this->emotes.at(index.row())); } virtual int rowCount(const QModelIndex &parent) const override { - return 1; + return this->emotes.size(); } + + QVector emotes; }; class CompletionManager { - CompletionManager(); + CompletionManager(EmoteManager &_emoteManager); + + EmoteManager &emoteManager; public: CompletionModel *createModel(const std::string &channelName); - void updateModel(CompletionModel *model, const std::string &channelName); + void updateModel(CompletionModel *model, const std::string &channelName = std::string()); friend class Application; }; diff --git a/src/emotemanager.cpp b/src/emotemanager.cpp index 438f00ae5..eded4ed5a 100644 --- a/src/emotemanager.cpp +++ b/src/emotemanager.cpp @@ -1,4 +1,5 @@ #include "emotemanager.hpp" +#include "common.hpp" #include "resources.hpp" #include "util/urlfetch.hpp" #include "windowmanager.hpp" @@ -25,6 +26,12 @@ EmoteManager::EmoteManager(WindowManager &_windowManager, Resources &_resources) , resources(_resources) { // Note: Do not use this->resources in ctor + pajlada::Settings::Setting roomID( + "/accounts/current/roomID", "", pajlada::Settings::SettingOption::DoNotWriteToJSON); + + roomID.getValueChangedSignal().connect([this](const std::string &roomID) { + this->refreshTwitchEmotes(roomID); // + }); } void EmoteManager::loadGlobalEmotes() @@ -48,6 +55,7 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName) QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString(); + std::vector codes; for (const QJsonValue &emoteNode : emotesNode) { QJsonObject emoteObject = emoteNode.toObject(); @@ -67,7 +75,10 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName) this->bttvChannelEmotes.insert(code, emote); channelEmoteMap.insert(code, emote); + codes.push_back(code.toStdString()); } + + this->bttvChannelEmoteCodes[channelName.toStdString()] = codes; }); } @@ -84,6 +95,7 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName) auto setsNode = rootNode.value("sets").toObject(); + std::vector codes; for (const QJsonValue &setNode : setsNode) { auto emotesNode = setNode.toObject().value("emoticons").toArray(); @@ -105,7 +117,10 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName) this->ffzChannelEmotes.insert(code, emote); channelEmoteMap.insert(code, emote); + codes.push_back(code.toStdString()); } + + this->ffzChannelEmoteCodes[channelName.toStdString()] = codes; } }); } @@ -279,9 +294,61 @@ void EmoteManager::parseEmojis(std::vector> &pars } } +void EmoteManager::refreshTwitchEmotes(const std::string &roomID) +{ + std::string oauthClient = + pajlada::Settings::Setting::get("/accounts/" + roomID + "/oauthClient"); + std::string oauthToken = + pajlada::Settings::Setting::get("/accounts/" + roomID + "/oauthToken"); + + TwitchAccountEmoteData &emoteData = this->twitchAccountEmotes[roomID]; + + if (emoteData.filled) { + qDebug() << "Already loaded for room id " << qS(roomID); + return; + } + + qDebug() << "Loading emotes for room id " << qS(roomID); + + if (oauthClient.empty() || oauthToken.empty()) { + qDebug() << "Missing oauth client/token"; + return; + } + + // api:v5 + QString url("https://api.twitch.tv/kraken/users/" + qS(roomID) + + "/emotes?api_version=5&oauth_token=" + qS(oauthToken) + + "&client_id=" + qS(oauthClient)); + + qDebug() << url; + + util::urlFetchJSONTimeout( + url, + [=, &emoteData](QJsonObject &root) { + emoteData.emoteSets.clear(); + emoteData.emoteCodes.clear(); + + auto emoticonSets = root.value("emoticon_sets").toObject(); + for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { + std::string emoteSetString = it.key().toStdString(); + QJsonArray emoteSetList = it.value().toArray(); + + for (QJsonValue emoteValue : emoteSetList) { + QJsonObject emoticon = emoteValue.toObject(); + std::string id = emoticon["id"].toString().toStdString(); + std::string code = emoticon["code"].toString().toStdString(); + emoteData.emoteSets[emoteSetString].push_back({id, code}); + emoteData.emoteCodes.push_back(code); + } + } + + emoteData.filled = true; + }, + 3000); +} + void EmoteManager::loadBTTVEmotes() { - // bttv QNetworkAccessManager *manager = new QNetworkAccessManager(); QUrl url("https://api.betterttv.net/2/emotes"); @@ -299,6 +366,7 @@ void EmoteManager::loadBTTVEmotes() QString linkTemplate = "https:" + root.value("urlTemplate").toString(); + std::vector codes; for (const QJsonValue &emote : emotes) { QString id = emote.toObject().value("id").toString(); QString code = emote.toObject().value("code").toString(); @@ -311,7 +379,10 @@ void EmoteManager::loadBTTVEmotes() this->bttvGlobalEmotes.insert( code, new LazyLoadedImage(*this, this->windowManager, url, 1, code, code + "\nGlobal BTTV Emote")); + codes.push_back(code.toStdString()); } + + this->bttvGlobalEmoteCodes = codes; } reply->deleteLater(); @@ -321,7 +392,6 @@ void EmoteManager::loadBTTVEmotes() void EmoteManager::loadFFZEmotes() { - // ffz QNetworkAccessManager *manager = new QNetworkAccessManager(); QUrl url("https://api.frankerfacez.com/v1/set/global"); @@ -337,6 +407,7 @@ void EmoteManager::loadFFZEmotes() auto sets = root.value("sets").toObject(); + std::vector codes; for (const QJsonValue &set : sets) { auto emoticons = set.toObject().value("emoticons").toArray(); @@ -354,7 +425,10 @@ void EmoteManager::loadFFZEmotes() this->ffzGlobalEmotes.insert( code, new LazyLoadedImage(*this, this->windowManager, url1, 1, code, code + "\nGlobal FFZ Emote")); + codes.push_back(code.toStdString()); } + + this->ffzGlobalEmoteCodes = codes; } } diff --git a/src/emotemanager.hpp b/src/emotemanager.hpp index 1cce56a2b..7324ce67e 100644 --- a/src/emotemanager.hpp +++ b/src/emotemanager.hpp @@ -5,6 +5,7 @@ #include "concurrentmap.hpp" #include "emojis.hpp" #include "messages/lazyloadedimage.hpp" +#include "signalvector.hpp" #include "twitch/emotevalue.hpp" #include @@ -88,11 +89,26 @@ private: public: void parseEmojis(std::vector> &parsedWords, const QString &text); -private: /// Twitch emotes - // username emote code - ConcurrentStdMap> twitchAccountEmotes; + void refreshTwitchEmotes(const std::string &roomID); + struct TwitchAccountEmoteData { + struct TwitchEmote { + std::string id; + std::string code; + }; + + // emote set + std::map> emoteSets; + + std::vector emoteCodes; + + bool filled = false; + }; + + std::map twitchAccountEmotes; + +private: // emote code ConcurrentMap _twitchEmotes; @@ -105,6 +121,9 @@ private: public: ConcurrentMap bttvChannels; EmoteMap bttvGlobalEmotes; + SignalVector bttvGlobalEmoteCodes; + // roomID + std::map> bttvChannelEmoteCodes; EmoteMap _bttvChannelEmoteFromCaches; private: @@ -116,6 +135,8 @@ private: public: ConcurrentMap ffzChannels; EmoteMap ffzGlobalEmotes; + SignalVector ffzGlobalEmoteCodes; + std::map> ffzChannelEmoteCodes; private: ConcurrentMap _ffzChannelEmoteFromCaches; diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 6dfbfef33..4aa3ba30c 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -79,8 +79,6 @@ Communi::IrcConnection *IrcManager::createConnection(bool doRead) this->refreshIgnoredUsers(username, oauthClient, oauthToken); } - this->refreshTwitchEmotes(username, oauthClient, oauthToken); - if (doRead) { connection->sendCommand( Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership")); @@ -130,34 +128,6 @@ void IrcManager::refreshIgnoredUsers(const QString &username, const QString &oau }); } -void IrcManager::refreshTwitchEmotes(const QString &username, const QString &oauthClient, - const QString &oauthToken) -{ - QString url("https://api.twitch.tv/kraken/users/" + username + - "/emotes?oauth_token=" + oauthToken + "&client_id=" + oauthClient); - - if (true) { - util::urlFetchJSONTimeout( - url, - [=](QJsonObject &root) { - // nextLink = - // root.value("_links").toObject().value("next").toString(); - - auto blocks = root.value("blocks").toArray(); - - _twitchBlockedUsersMutex.lock(); - for (QJsonValue block : blocks) { - QJsonObject user = block.toObject().value("user").toObject(); - // display_name - _twitchBlockedUsers.insert(user.value("name").toString().toLower(), true); - } - _twitchBlockedUsersMutex.unlock(); - qDebug() << "XD"; - }, - 3000, &this->networkAccessManager); - } -} - void IrcManager::beginConnecting() { uint32_t generation = ++this->connectionGeneration; @@ -305,6 +275,7 @@ void IrcManager::handleRoomStateMessage(Communi::IrcMessage *message) if (iterator != tags.end()) { std::string roomID = iterator.value().toString().toStdString(); + this->resources.loadChannelData(roomID); } } diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index a1f597fa0..b658ae413 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -76,8 +76,6 @@ private: void refreshIgnoredUsers(const QString &username, const QString &oauthClient, const QString &oauthToken); - void refreshTwitchEmotes(const QString &username, const QString &oauthClient, - const QString &oauthToken); void beginConnecting(); diff --git a/src/widgets/chatwidget.cpp b/src/widgets/chatwidget.cpp index 8c7b06725..92e2331a3 100644 --- a/src/widgets/chatwidget.cpp +++ b/src/widgets/chatwidget.cpp @@ -37,6 +37,7 @@ static int index = 0; ChatWidget::ChatWidget(ChannelManager &_channelManager, NotebookPage *parent) : BaseWidget(parent) , channelManager(_channelManager) + , completionManager(parent->completionManager) , channel(_channelManager.emptyChannel) , vbox(this) , header(this) diff --git a/src/widgets/chatwidget.hpp b/src/widgets/chatwidget.hpp index 607cfda53..64a038ee7 100644 --- a/src/widgets/chatwidget.hpp +++ b/src/widgets/chatwidget.hpp @@ -21,6 +21,7 @@ namespace chatterino { class ChannelManager; class ColorScheme; +class CompletionManager; namespace widgets { @@ -59,9 +60,11 @@ public: protected: virtual void paintEvent(QPaintEvent *) override; -private: +public: ChannelManager &channelManager; + CompletionManager &completionManager; +private: void setChannel(std::shared_ptr newChannel); void detachChannel(); diff --git a/src/widgets/chatwidgetinput.cpp b/src/widgets/chatwidgetinput.cpp index 9bc7a329e..acddf1af6 100644 --- a/src/widgets/chatwidgetinput.cpp +++ b/src/widgets/chatwidgetinput.cpp @@ -1,6 +1,7 @@ #include "widgets/chatwidgetinput.hpp" #include "chatwidget.hpp" #include "colorscheme.hpp" +#include "completionmanager.hpp" #include "ircmanager.hpp" #include "settingsmanager.hpp" @@ -45,8 +46,8 @@ ChatWidgetInput::ChatWidgetInput(ChatWidget *_chatWidget) this->refreshTheme(); this->setMessageLengthVisible(SettingsManager::getInstance().showMessageLength.get()); - // TODO: Fill in this QCompleter model using our CompletionManager - auto completer = new QCompleter(); + auto completer = new QCompleter( + this->chatWidget->completionManager.createModel(this->chatWidget->channelName)); this->textInput.setCompleter(completer); diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 3e96f3c7d..99c5bce26 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -19,10 +19,12 @@ namespace chatterino { namespace widgets { -MainWindow::MainWindow(ChannelManager &_channelManager, ColorScheme &_colorScheme) +MainWindow::MainWindow(ChannelManager &_channelManager, ColorScheme &_colorScheme, + CompletionManager &_completionManager) : BaseWidget(_colorScheme, nullptr) , channelManager(_channelManager) , colorScheme(_colorScheme) + , completionManager(_completionManager) , notebook(this->channelManager, this) , windowGeometry("/windows/0/geometry") { diff --git a/src/widgets/mainwindow.hpp b/src/widgets/mainwindow.hpp index 2a722ab51..37decd0fa 100644 --- a/src/widgets/mainwindow.hpp +++ b/src/widgets/mainwindow.hpp @@ -17,6 +17,7 @@ namespace chatterino { class ChannelManager; class ColorScheme; +class CompletionManager; namespace widgets { @@ -25,7 +26,8 @@ class MainWindow : public BaseWidget Q_OBJECT public: - explicit MainWindow(ChannelManager &_channelManager, ColorScheme &_colorScheme); + explicit MainWindow(ChannelManager &_channelManager, ColorScheme &_colorScheme, + CompletionManager &_completionManager); ~MainWindow(); void layoutVisibleChatWidgets(Channel *channel = nullptr); @@ -48,6 +50,7 @@ private: ChannelManager &channelManager; ColorScheme &colorScheme; + CompletionManager &completionManager; Notebook notebook; bool loaded = false; @@ -144,6 +147,8 @@ private: }; pajlada::Settings::Setting windowGeometry; + + friend class Notebook; }; } // namespace widgets diff --git a/src/widgets/notebook.cpp b/src/widgets/notebook.cpp index f9d2ba78d..fa81ec102 100644 --- a/src/widgets/notebook.cpp +++ b/src/widgets/notebook.cpp @@ -1,5 +1,6 @@ #include "widgets/notebook.hpp" #include "colorscheme.hpp" +#include "widgets/mainwindow.hpp" #include "widgets/notebookbutton.hpp" #include "widgets/notebookpage.hpp" #include "widgets/notebooktab.hpp" @@ -18,9 +19,10 @@ namespace chatterino { namespace widgets { -Notebook::Notebook(ChannelManager &_channelManager, BaseWidget *parent) +Notebook::Notebook(ChannelManager &_channelManager, MainWindow *parent) : BaseWidget(parent) , channelManager(_channelManager) + , completionManager(parent->completionManager) , addButton(this) , settingsButton(this) , userButton(this) diff --git a/src/widgets/notebook.hpp b/src/widgets/notebook.hpp index d510584dd..2b6085334 100644 --- a/src/widgets/notebook.hpp +++ b/src/widgets/notebook.hpp @@ -12,10 +12,12 @@ namespace chatterino { class ChannelManager; -class ColorScheme; +class CompletionManager; namespace widgets { +class MainWindow; + class Notebook : public BaseWidget { Q_OBJECT @@ -23,7 +25,7 @@ class Notebook : public BaseWidget public: enum HighlightType { none, highlighted, newMessage }; - explicit Notebook(ChannelManager &_channelManager, BaseWidget *parent); + explicit Notebook(ChannelManager &_channelManager, MainWindow *parent); NotebookPage *addPage(bool select = false); @@ -50,9 +52,11 @@ public slots: void usersButtonClicked(); void addPageButtonClicked(); -private: +public: ChannelManager &channelManager; + CompletionManager &completionManager; +private: QList pages; NotebookButton addButton; diff --git a/src/widgets/notebookpage.cpp b/src/widgets/notebookpage.cpp index 7563487e4..fbec4e616 100644 --- a/src/widgets/notebookpage.cpp +++ b/src/widgets/notebookpage.cpp @@ -25,6 +25,7 @@ std::pair NotebookPage::dropPosition = std::pair(-1, -1); NotebookPage::NotebookPage(ChannelManager &_channelManager, Notebook *parent, NotebookTab *_tab) : BaseWidget(parent->colorScheme, parent) , channelManager(_channelManager) + , completionManager(parent->completionManager) , tab(_tab) , dropPreview(this) { diff --git a/src/widgets/notebookpage.hpp b/src/widgets/notebookpage.hpp index 6f8410a3c..65ad64c4d 100644 --- a/src/widgets/notebookpage.hpp +++ b/src/widgets/notebookpage.hpp @@ -18,6 +18,7 @@ namespace chatterino { class ChannelManager; +class CompletionManager; namespace widgets { @@ -29,6 +30,7 @@ public: NotebookPage(ChannelManager &_channelManager, Notebook *parent, NotebookTab *_tab); ChannelManager &channelManager; + CompletionManager &completionManager; std::pair removeFromLayout(ChatWidget *widget); void addToLayout(ChatWidget *widget, std::pair position); diff --git a/src/windowmanager.cpp b/src/windowmanager.cpp index b3d7fd594..ac0dbc9f4 100644 --- a/src/windowmanager.cpp +++ b/src/windowmanager.cpp @@ -10,9 +10,11 @@ namespace chatterino { -WindowManager::WindowManager(ChannelManager &_channelManager, ColorScheme &_colorScheme) +WindowManager::WindowManager(ChannelManager &_channelManager, ColorScheme &_colorScheme, + CompletionManager &_completionManager) : channelManager(_channelManager) , colorScheme(_colorScheme) + , completionManager(_completionManager) { } @@ -56,7 +58,8 @@ widgets::MainWindow &WindowManager::getMainWindow() std::lock_guard lock(this->windowMutex); if (this->mainWindow == nullptr) { - this->mainWindow = new widgets::MainWindow(this->channelManager, this->colorScheme); + this->mainWindow = new widgets::MainWindow(this->channelManager, this->colorScheme, + this->completionManager); } return *this->mainWindow; diff --git a/src/windowmanager.hpp b/src/windowmanager.hpp index 4cdb05520..cbf18ae23 100644 --- a/src/windowmanager.hpp +++ b/src/windowmanager.hpp @@ -8,14 +8,17 @@ namespace chatterino { class ChannelManager; class ColorScheme; +class CompletionManager; class WindowManager { public: - explicit WindowManager(ChannelManager &_channelManager, ColorScheme &_colorScheme); + explicit WindowManager(ChannelManager &_channelManager, ColorScheme &_colorScheme, + CompletionManager &_completionManager); ChannelManager &channelManager; ColorScheme &colorScheme; + CompletionManager &completionManager; void layoutVisibleChatWidgets(Channel *channel = nullptr); void repaintVisibleChatWidgets(Channel *channel = nullptr);