More progress on tab-complete

There are missing parts to the "account-based" emotes that needs to be
completed before emote completion can be considered done. For now, when
I've been testing, I've been manually injecting the oauthClient and
oauthToken to the settings file with the `user_subscriptions` scope
This commit is contained in:
Rasmus Karlsson 2017-07-23 14:16:13 +02:00
parent e4fc6c25e6
commit 3bf111a091
19 changed files with 212 additions and 69 deletions

View file

@ -1,5 +1,7 @@
#include "accountmanager.hpp"
#include <pajlada/settings/setting.hpp>
namespace chatterino {
namespace {
@ -25,6 +27,9 @@ AccountManager::AccountManager()
if (!envUsername.isEmpty() && !envOauthToken.isEmpty()) {
this->addTwitchUser(twitch::TwitchUser(envUsername, envOauthToken, ""));
}
pajlada::Settings::Setting<std::string>::set(
"/accounts/current/roomID", "11148817", pajlada::Settings::SettingOption::DoNotWriteToJSON);
}
twitch::TwitchUser &AccountManager::getTwitchAnon()

View file

@ -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)

View file

@ -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<std::string> &bttvGlobalEmoteCodes = this->emoteManager.bttvGlobalEmoteCodes;
for (const auto &m : bttvGlobalEmoteCodes) {
model->emotes.push_back(qS(m));
}
std::vector<std::string> &ffzGlobalEmoteCodes = this->emoteManager.ffzGlobalEmoteCodes;
for (const auto &m : ffzGlobalEmoteCodes) {
model->emotes.push_back(qS(m));
}
std::vector<std::string> &bttvChannelEmoteCodes =
this->emoteManager.bttvChannelEmoteCodes[channelName];
for (const auto &m : bttvChannelEmoteCodes) {
model->emotes.push_back(qS(m));
}
std::vector<std::string> &ffzChannelEmoteCodes =
this->emoteManager.ffzChannelEmoteCodes[channelName];
for (const auto &m : ffzChannelEmoteCodes) {
model->emotes.push_back(qS(m));
}
}
} // namespace chatterino

View file

@ -1,12 +1,15 @@
#pragma once
#include <QAbstractItemModel>
#include <QAbstractListModel>
#include <QVector>
#include <string>
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<QString> 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;
};

View file

@ -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<std::string> 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<std::string> 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<std::string> 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<std::tuple<EmoteData, QString>> &pars
}
}
void EmoteManager::refreshTwitchEmotes(const std::string &roomID)
{
std::string oauthClient =
pajlada::Settings::Setting<std::string>::get("/accounts/" + roomID + "/oauthClient");
std::string oauthToken =
pajlada::Settings::Setting<std::string>::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<std::string> 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<std::string> 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;
}
}

View file

@ -5,6 +5,7 @@
#include "concurrentmap.hpp"
#include "emojis.hpp"
#include "messages/lazyloadedimage.hpp"
#include "signalvector.hpp"
#include "twitch/emotevalue.hpp"
#include <QMap>
@ -88,11 +89,26 @@ private:
public:
void parseEmojis(std::vector<std::tuple<EmoteData, QString>> &parsedWords, const QString &text);
private:
/// Twitch emotes
// username emote code
ConcurrentStdMap<QString, std::vector<QString>> twitchAccountEmotes;
void refreshTwitchEmotes(const std::string &roomID);
struct TwitchAccountEmoteData {
struct TwitchEmote {
std::string id;
std::string code;
};
// emote set
std::map<std::string, std::vector<TwitchEmote>> emoteSets;
std::vector<std::string> emoteCodes;
bool filled = false;
};
std::map<std::string, TwitchAccountEmoteData> twitchAccountEmotes;
private:
// emote code
ConcurrentMap<QString, twitch::EmoteValue *> _twitchEmotes;
@ -105,6 +121,9 @@ private:
public:
ConcurrentMap<QString, EmoteMap> bttvChannels;
EmoteMap bttvGlobalEmotes;
SignalVector<std::string> bttvGlobalEmoteCodes;
// roomID
std::map<std::string, SignalVector<std::string>> bttvChannelEmoteCodes;
EmoteMap _bttvChannelEmoteFromCaches;
private:
@ -116,6 +135,8 @@ private:
public:
ConcurrentMap<QString, EmoteMap> ffzChannels;
EmoteMap ffzGlobalEmotes;
SignalVector<std::string> ffzGlobalEmoteCodes;
std::map<std::string, SignalVector<std::string>> ffzChannelEmoteCodes;
private:
ConcurrentMap<int, EmoteData> _ffzChannelEmoteFromCaches;

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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)

View file

@ -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<Channel> newChannel);
void detachChannel();

View file

@ -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);

View file

@ -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")
{

View file

@ -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<QRectWrapper, QRectWrapper> windowGeometry;
friend class Notebook;
};
} // namespace widgets

View file

@ -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)

View file

@ -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<NotebookPage *> pages;
NotebookButton addButton;

View file

@ -25,6 +25,7 @@ std::pair<int, int> NotebookPage::dropPosition = std::pair<int, int>(-1, -1);
NotebookPage::NotebookPage(ChannelManager &_channelManager, Notebook *parent, NotebookTab *_tab)
: BaseWidget(parent->colorScheme, parent)
, channelManager(_channelManager)
, completionManager(parent->completionManager)
, tab(_tab)
, dropPreview(this)
{

View file

@ -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<int, int> removeFromLayout(ChatWidget *widget);
void addToLayout(ChatWidget *widget, std::pair<int, int> position);

View file

@ -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<std::mutex> 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;

View file

@ -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);