From 07f39f2667c82a5e63789ecd0b14dc270839665b Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 01:27:37 +0100 Subject: [PATCH 01/15] Remove offline chat debug code --- src/ircmanager.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 9a1bcf3d..7a205cf2 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -184,29 +184,6 @@ void IrcManager::sendMessage(const QString &channelName, const QString &message) } this->connectionMutex.unlock(); - - // DEBUGGING - /* - Communi::IrcPrivateMessage msg(this->readConnection.get()); - - QStringList params{"#pajlada", message}; - - qDebug() << params; - - if (message == "COMIC SANS LOL") { - FontManager::getInstance().currentFontFamily = "Comic Sans MS"; - } else if (message == "ARIAL LOL") { - FontManager::getInstance().currentFontFamily = "Arial"; - } else if (message == "WINGDINGS LOL") { - FontManager::getInstance().currentFontFamily = "Wingdings"; - } - - msg.setParameters(params); - - msg.setPrefix("pajlada!pajlada@pajlada"); - - this->privateMessageReceived(&msg); - */ } void IrcManager::joinChannel(const QString &channelName) From b5bb49e8e5d11ed5304866ae8c7099895ea6732f Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 01:57:34 +0100 Subject: [PATCH 02/15] Add method for executing lambda in QObject's thread --- src/asyncexec.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/asyncexec.hpp b/src/asyncexec.hpp index ba36b268..7899676c 100644 --- a/src/asyncexec.hpp +++ b/src/asyncexec.hpp @@ -25,3 +25,30 @@ public: private: std::function action; }; + +// Taken from +// https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style +// Qt 5/4 - preferred, has least allocations +template +static void postToThread(F &&fun, QObject *obj = qApp) +{ + struct Event : public QEvent { + using Fun = typename std::decay::type; + Fun fun; + Event(Fun &&fun) + : QEvent(QEvent::None) + , fun(std::move(fun)) + { + } + Event(const Fun &fun) + : QEvent(QEvent::None) + , fun(fun) + { + } + ~Event() + { + fun(); + } + }; + QCoreApplication::postEvent(obj, new Event(std::forward(fun))); +} From a8afdf45652695617a9024a813aac9000fa793c3 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 02:09:51 +0100 Subject: [PATCH 03/15] remove some debug output --- src/emotemanager.cpp | 1 - src/widgets/helper/channelview.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/emotemanager.cpp b/src/emotemanager.cpp index f9b6e9bf..fcdb2348 100644 --- a/src/emotemanager.cpp +++ b/src/emotemanager.cpp @@ -505,7 +505,6 @@ void EmoteManager::loadFFZEmotes() EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteName) { return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &id] { - qDebug() << "added twitch emote: " << id; qreal scale; QString url = getTwitchEmoteLink(id, scale); return new LazyLoadedImage(*this, this->windowManager, url, scale, emoteName, diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 55109204..159d9d01 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -802,7 +802,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) float distance = util::distanceBetweenPoints(this->lastPressPosition, event->screenPos()); - qDebug() << "Distance: " << distance; + // qDebug() << "Distance: " << distance; if (fabsf(distance) > 15.f) { // It wasn't a proper click, so we don't care about that here From a372bae80ddaea3aa95481425ba2195ece34b0ac Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 02:21:06 +0100 Subject: [PATCH 04/15] Change the way Twitch accounts are stored in AccountManager This is done in a way which should simplify abstracting it to other types of accounts if needed in the future Remove comment about removing singletons - we're keeping them (and probably restoring some) IrcManager now updates its "account" reference automatically through the AccountManager.Twitch.userChanged-signal Remove unused IrcManager getUser-method IrcManager::beginConnecting is no longer called asynchronously. This might want to be reverted in a more controlled asynchronous manner. User Accounts are now stored as Shared Pointers instead of using references/copies everywhere --- src/accountmanager.cpp | 149 ++++++++++++++++++--------------- src/accountmanager.hpp | 50 ++++++----- src/application.cpp | 3 - src/ircmanager.cpp | 50 ++++++----- src/ircmanager.hpp | 7 +- src/util/urlfetch.hpp | 13 ++- src/widgets/accountpopup.cpp | 40 +++++++-- src/widgets/settingsdialog.cpp | 11 +-- 8 files changed, 187 insertions(+), 136 deletions(-) diff --git a/src/accountmanager.cpp b/src/accountmanager.cpp index c5dc5145..04ec59a2 100644 --- a/src/accountmanager.cpp +++ b/src/accountmanager.cpp @@ -1,7 +1,6 @@ #include "accountmanager.hpp" #include "common.hpp" - -#include +#include "debug/log.hpp" namespace chatterino { @@ -19,18 +18,84 @@ inline QString getEnvString(const char *target) } // namespace -AccountManager::AccountManager() - : currentUser("/accounts/current", "") - , twitchAnonymousUser("justinfan64537", "", "") +std::shared_ptr TwitchAccountManager::getCurrent() { + if (!this->currentUser) { + return this->anonymousUser; + } + + return this->currentUser; +} + +std::vector TwitchAccountManager::getUsernames() const +{ + std::vector userNames; + + std::lock_guard lock(this->mutex); + + for (const auto &user : this->users) { + userNames.push_back(user->getUserName()); + } + + return userNames; +} + +std::shared_ptr TwitchAccountManager::findUserByUsername( + const QString &username) const +{ + std::lock_guard lock(this->mutex); + + for (const auto &user : this->users) { + if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) { + return user; + } + } + + return nullptr; +} + +bool TwitchAccountManager::userExists(const QString &username) const +{ + return this->findUserByUsername(username) != nullptr; +} + +bool TwitchAccountManager::addUser(std::shared_ptr user) +{ + if (this->userExists(user->getNickName())) { + // User already exists in user list + return false; + } + + std::lock_guard lock(this->mutex); + + this->users.push_back(user); + + return true; +} + +AccountManager::AccountManager() +{ + this->Twitch.anonymousUser.reset(new twitch::TwitchUser("justinfan64537", "", "")); + + this->Twitch.currentUsername.getValueChangedSignal().connect([this](const auto &newValue) { + QString newUsername(QString::fromStdString(newValue)); + auto user = this->Twitch.findUserByUsername(newUsername); + if (user) { + debug::Log("[AccountManager:currentUsernameChanged] User successfully updated to {}", + newUsername); + // XXX: Should we set the user regardless if the username is found or not? + // I can see the logic in setting it to nullptr if the `currentUsername` value has been + // set to "" or an invalid username + this->Twitch.currentUser = user; + this->Twitch.userChanged.invoke(); + } + }); } void AccountManager::load() { auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts"); - bool first = true; - for (const auto &uid : keys) { if (uid == "current") { continue; @@ -49,74 +114,20 @@ void AccountManager::load() continue; } - if (first) { - this->setCurrentTwitchUser(qS(username)); - first = false; - } + auto user = + std::make_shared(qS(username), qS(oauthToken), qS(clientID)); - twitch::TwitchUser user(qS(username), qS(oauthToken), qS(clientID)); - - this->addTwitchUser(user); + this->Twitch.addUser(user); printf("Adding user %s(%s)\n", username.c_str(), userID.c_str()); } -} -twitch::TwitchUser &AccountManager::getTwitchAnon() -{ - return this->twitchAnonymousUser; -} - -twitch::TwitchUser &AccountManager::getTwitchUser() -{ - std::lock_guard lock(this->twitchUsersMutex); - - if (this->twitchUsers.size() == 0) { - return this->getTwitchAnon(); + auto currentUser = this->Twitch.findUserByUsername( + QString::fromStdString(this->Twitch.currentUsername.getValue())); + if (currentUser) { + this->Twitch.currentUser = currentUser; + this->Twitch.userChanged.invoke(); } - - QString currentUsername = QString::fromStdString(this->currentUser); - - for (auto &user : this->twitchUsers) { - if (user.getUserName() == currentUsername) { - return user; - } - } - - return this->twitchUsers.front(); -} - -void AccountManager::setCurrentTwitchUser(const QString &username) -{ - this->currentUser.setValue(username.toStdString()); -} - -std::vector AccountManager::getTwitchUsers() -{ - std::lock_guard lock(this->twitchUsersMutex); - - return std::vector(this->twitchUsers); -} - -bool AccountManager::removeTwitchUser(const QString &userName) -{ - std::lock_guard lock(this->twitchUsersMutex); - - for (auto it = this->twitchUsers.begin(); it != this->twitchUsers.end(); it++) { - if ((*it).getUserName() == userName) { - this->twitchUsers.erase(it); - return true; - } - } - - return false; -} - -void AccountManager::addTwitchUser(const twitch::TwitchUser &user) -{ - std::lock_guard lock(this->twitchUsersMutex); - - this->twitchUsers.push_back(user); } } // namespace chatterino diff --git a/src/accountmanager.hpp b/src/accountmanager.hpp index 970e7972..f496c7ff 100644 --- a/src/accountmanager.hpp +++ b/src/accountmanager.hpp @@ -9,6 +9,34 @@ namespace chatterino { +class AccountManager; + +class TwitchAccountManager +{ +public: + // Returns the current twitchUsers, or the anonymous user if we're not currently logged in + std::shared_ptr getCurrent(); + + std::vector getUsernames() const; + + std::shared_ptr findUserByUsername(const QString &username) const; + bool userExists(const QString &username) const; + + pajlada::Settings::Setting currentUsername = {"/accounts/current", ""}; + pajlada::Signals::NoArgSignal userChanged; + +private: + bool addUser(std::shared_ptr user); + + std::shared_ptr currentUser; + + std::shared_ptr anonymousUser; + std::vector> users; + mutable std::mutex mutex; + + friend class AccountManager; +}; + class AccountManager { public: @@ -20,30 +48,10 @@ public: void load(); - twitch::TwitchUser &getTwitchAnon(); - - // Returns first user from twitchUsers, or twitchAnonymousUser if twitchUsers is empty - twitch::TwitchUser &getTwitchUser(); - - // Return a copy of the current available twitch users - std::vector getTwitchUsers(); - - // Remove twitch user with the given username - bool removeTwitchUser(const QString &userName); - - void setCurrentTwitchUser(const QString &username); - - // Add twitch user to the list of available twitch users - void addTwitchUser(const twitch::TwitchUser &user); + TwitchAccountManager Twitch; private: AccountManager(); - - pajlada::Settings::Setting currentUser; - - twitch::TwitchUser twitchAnonymousUser; - std::vector twitchUsers; - std::mutex twitchUsersMutex; }; } // namespace chatterino diff --git a/src/application.cpp b/src/application.cpp index 9ae0b6cc..5949c0d4 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -18,7 +18,6 @@ Application::Application() , channelManager(this->windowManager, this->emoteManager, this->ircManager) , ircManager(this->channelManager, this->resources, this->emoteManager, this->windowManager) { - // TODO(pajlada): Get rid of all singletons logging::init(); SettingsManager::getInstance().load(); @@ -29,8 +28,6 @@ Application::Application() AccountManager::getInstance().load(); - this->ircManager.setUser(AccountManager::getInstance().getTwitchUser()); - // XXX SettingsManager::getInstance().updateWordTypeMask(); diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 7a205cf2..d8c5b9cf 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -3,6 +3,7 @@ #include "asyncexec.hpp" #include "channel.hpp" #include "channelmanager.hpp" +#include "debug/log.hpp" #include "emotemanager.hpp" #include "messages/messageparseargs.hpp" #include "twitch/twitchmessagebuilder.hpp" @@ -31,21 +32,18 @@ IrcManager::IrcManager(ChannelManager &_channelManager, Resources &_resources, , resources(_resources) , emoteManager(_emoteManager) , windowManager(_windowManager) - , account(AccountManager::getInstance().getTwitchAnon()) - , currentUser("/accounts/current") { - this->currentUser.getValueChangedSignal().connect([](const auto &newUsername) { - // TODO: Implement - qDebug() << "Current user changed, fetch new credentials and reconnect"; + AccountManager::getInstance().Twitch.userChanged.connect([this]() { + this->setUser(AccountManager::getInstance().Twitch.getCurrent()); + + debug::Log("[IrcManager] Reconnecting to Twitch IRC as new user {}", + this->account->getUserName()); + + postToThread([this] { this->connect(); }); }); } -const twitch::TwitchUser &IrcManager::getUser() const -{ - return this->account; -} - -void IrcManager::setUser(const twitch::TwitchUser &account) +void IrcManager::setUser(std::shared_ptr account) { this->account = account; } @@ -54,11 +52,16 @@ void IrcManager::connect() { disconnect(); - async_exec([this] { beginConnecting(); }); + // XXX(pajlada): Disabled the async_exec for now, because if we happen to run the + // `beginConnecting` function in a different thread than last time, we won't be able to connect + // because we can't clean up the previous connection properly + // async_exec([this] { beginConnecting(); }); + this->beginConnecting(); } Communi::IrcConnection *IrcManager::createConnection(bool doRead) { + assert(this->account); Communi::IrcConnection *connection = new Communi::IrcConnection; if (doRead) { @@ -68,9 +71,9 @@ Communi::IrcConnection *IrcManager::createConnection(bool doRead) &IrcManager::privateMessageReceived); } - QString username = this->account.getUserName(); - QString oauthClient = this->account.getOAuthClient(); - QString oauthToken = this->account.getOAuthToken(); + QString username = this->account->getUserName(); + QString oauthClient = this->account->getOAuthClient(); + QString oauthToken = this->account->getOAuthToken(); if (!oauthToken.startsWith("oauth:")) { oauthToken.prepend("oauth:"); } @@ -79,7 +82,7 @@ Communi::IrcConnection *IrcManager::createConnection(bool doRead) connection->setNickName(username); connection->setRealName(username); - if (!this->account.isAnon()) { + if (!this->account->isAnon()) { connection->setPassword(oauthToken); this->refreshIgnoredUsers(username, oauthClient, oauthToken); @@ -310,9 +313,11 @@ bool IrcManager::isTwitchBlockedUser(QString const &username) bool IrcManager::tryAddIgnoredUser(QString const &username, QString &errorMessage) { - QUrl url("https://api.twitch.tv/kraken/users/" + this->account.getUserName() + "/blocks/" + - username + "?oauth_token=" + this->account.getOAuthToken() + - "&client_id=" + this->account.getOAuthClient()); + assert(this->account); + + QUrl url("https://api.twitch.tv/kraken/users/" + this->account->getUserName() + "/blocks/" + + username + "?oauth_token=" + this->account->getOAuthToken() + + "&client_id=" + this->account->getOAuthClient()); QNetworkRequest request(url); auto reply = this->networkAccessManager.put(request, QByteArray()); @@ -342,9 +347,10 @@ void IrcManager::addIgnoredUser(QString const &username) bool IrcManager::tryRemoveIgnoredUser(QString const &username, QString &errorMessage) { - QUrl url("https://api.twitch.tv/kraken/users/" + this->account.getUserName() + "/blocks/" + - username + "?oauth_token=" + this->account.getOAuthToken() + - "&client_id=" + this->account.getOAuthClient()); + assert(this->account); + QUrl url("https://api.twitch.tv/kraken/users/" + this->account->getUserName() + "/blocks/" + + username + "?oauth_token=" + this->account->getOAuthToken() + + "&client_id=" + this->account->getOAuthClient()); QNetworkRequest request(url); auto reply = this->networkAccessManager.deleteResource(request); diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index 94ad534c..87b8de5d 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -44,8 +44,7 @@ public: void joinChannel(const QString &channelName); void partChannel(const QString &channelName); - const twitch::TwitchUser &getUser() const; - void setUser(const twitch::TwitchUser &account); + void setUser(std::shared_ptr account); pajlada::Signals::Signal onPrivateMessage; @@ -56,9 +55,7 @@ public: private: // variables - twitch::TwitchUser account; - - pajlada::Settings::Setting currentUser; + std::shared_ptr account = nullptr; std::shared_ptr writeConnection = nullptr; diff --git a/src/util/urlfetch.hpp b/src/util/urlfetch.hpp index a7ebe4c1..f322a238 100644 --- a/src/util/urlfetch.hpp +++ b/src/util/urlfetch.hpp @@ -87,11 +87,18 @@ static void put(QUrl url, std::function successCallback) auto manager = new QNetworkAccessManager(); QNetworkRequest request(url); + auto &accountManager = AccountManager::getInstance(); + auto currentTwitchUser = accountManager.Twitch.getCurrent(); + QByteArray oauthToken; + if (currentTwitchUser) { + oauthToken = currentTwitchUser->getOAuthToken().toUtf8(); + } else { + // XXX(pajlada): Bail out? + } + request.setRawHeader("Client-ID", getDefaultClientID()); request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); - request.setRawHeader( - "Authorization", - "OAuth " + AccountManager::getInstance().getTwitchUser().getOAuthToken().toUtf8()); + request.setRawHeader("Authorization", "OAuth " + oauthToken); NetworkManager::urlPut(std::move(request), [=](QNetworkReply *reply) { if (reply->error() == QNetworkReply::NetworkError::NoError) { diff --git a/src/widgets/accountpopup.cpp b/src/widgets/accountpopup.cpp index d87f987b..49aa6f96 100644 --- a/src/widgets/accountpopup.cpp +++ b/src/widgets/accountpopup.cpp @@ -60,6 +60,15 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) sendCommand(this->_ui->mod, "/mod "); sendCommand(this->_ui->unMod, "/unmod "); + auto &accountManager = AccountManager::getInstance(); + QString userId; + QString userNickname; + auto currentTwitchUser = accountManager.Twitch.getCurrent(); + if (currentTwitchUser) { + userId = currentTwitchUser->getUserId(); + userNickname = currentTwitchUser->getNickName(); + } + QObject::connect(this->_ui->profile, &QPushButton::clicked, this, [=](){ QDesktopServices::openUrl(QUrl("https://twitch.tv/" + this->_ui->lblUsername->text())); @@ -76,7 +85,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) QObject::connect(this->_ui->follow, &QPushButton::clicked, this, [=](){ QUrl requestUrl("https://api.twitch.tv/kraken/users/" + - AccountManager::getInstance().getTwitchUser().getUserId() + + userId + "/follows/channels/" + this->userID); util::twitch::put(requestUrl,[](QJsonObject obj){ @@ -86,7 +95,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) QObject::connect(this->_ui->ignore, &QPushButton::clicked, this, [=](){ QUrl requestUrl("https://api.twitch.tv/kraken/users/" + - AccountManager::getInstance().getTwitchUser().getUserId() + + userId + "/blocks/" + this->userID); util::twitch::put(requestUrl,[](QJsonObject obj){ @@ -121,9 +130,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) hide(); // }); - util::twitch::getUserID(AccountManager::getInstance().getTwitchUser().getNickName(), this, + util::twitch::getUserID(userNickname, this, [=](const QString &id){ - AccountManager::getInstance().getTwitchUser().setUserId(id); + currentTwitchUser->setUserId(id); }); } @@ -185,12 +194,20 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl) void AccountPopupWidget::updatePermissions() { - if(this->_channel.get()->name == AccountManager::getInstance().getTwitchUser().getNickName()) + AccountManager &accountManager = AccountManager::getInstance(); + auto currentTwitchUser = accountManager.Twitch.getCurrent(); + if (!currentTwitchUser) { + // No twitch user set (should never happen) + return; + } + + if(this->_channel.get()->name == currentTwitchUser->getNickName()) { permission = permissions::Owner; } - else if(this->_channel->modList.contains(AccountManager::getInstance().getTwitchUser().getNickName())) + else if(this->_channel->modList.contains(currentTwitchUser->getNickName())) { + // XXX(pajlada): This might always trigger if user is anonymous (if nickName is empty?) permission = permissions::Mod; } } @@ -229,8 +246,15 @@ void AccountPopupWidget::focusOutEvent(QFocusEvent *event) void AccountPopupWidget::showEvent(QShowEvent *event) { - if(this->_ui->lblUsername->text() != AccountManager::getInstance().getTwitchUser().getNickName()) - { + AccountManager &accountManager = AccountManager::getInstance(); + auto currentTwitchUser = accountManager.Twitch.getCurrent(); + if (!currentTwitchUser) { + // No twitch user set (should never happen) + return; + } + + if(this->_ui->lblUsername->text() != currentTwitchUser->getNickName()) + { updateButtons(this->_ui->userLayout, true); if(permission != permissions::User) { diff --git a/src/widgets/settingsdialog.cpp b/src/widgets/settingsdialog.cpp index 583d39ae..b1f1c14f 100644 --- a/src/widgets/settingsdialog.cpp +++ b/src/widgets/settingsdialog.cpp @@ -129,13 +129,13 @@ QVBoxLayout *SettingsDialog::createAccountsTab() // listview auto listWidget = new QListWidget(this); - for (auto &user : AccountManager::getInstance().getTwitchUsers()) { - listWidget->addItem(user.getUserName()); + for (const auto &userName : AccountManager::getInstance().Twitch.getUsernames()) { + listWidget->addItem(userName); } + // Select the currently logged in user if (listWidget->count() > 0) { - const auto ¤tUser = AccountManager::getInstance().getTwitchUser(); - QString currentUsername = currentUser.getUserName(); + const QString ¤tUsername = AccountManager::getInstance().Twitch.getCurrent()->getUserName(); for (int i = 0; i < listWidget->count(); ++i) { QString itemText = listWidget->item(i)->text(); if (itemText.compare(currentUsername, Qt::CaseInsensitive) == 0) { @@ -147,7 +147,8 @@ QVBoxLayout *SettingsDialog::createAccountsTab() QObject::connect(listWidget, &QListWidget::clicked, this, [&, listWidget] { if (!listWidget->selectedItems().isEmpty()) { - AccountManager::getInstance().setCurrentTwitchUser(listWidget->currentItem()->text()); + QString newUsername = listWidget->currentItem()->text(); + AccountManager::getInstance().Twitch.currentUsername = newUsername.toStdString(); } }); From 676c7b90179a216c7fbc1d9d56bd3da9c3c4a943 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 02:22:35 +0100 Subject: [PATCH 05/15] Reformat some files according to `.clang-format` --- src/ircmanager.cpp | 14 ++-- src/widgets/accountpopup.cpp | 133 +++++++++++++-------------------- src/widgets/accountpopup.hpp | 7 +- src/widgets/settingsdialog.cpp | 3 +- 4 files changed, 65 insertions(+), 92 deletions(-) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index d8c5b9cf..fc19cbd4 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -292,14 +292,12 @@ void IrcManager::handleUserNoticeMessage(Communi::IrcMessage *message) void IrcManager::handleModeMessage(Communi::IrcMessage *message) { - auto channel = channelManager.getTwitchChannel(message->parameter(0).remove(0,1)); - if(message->parameter(1) == "+o") - { - channel->modList.append(message->parameter(2)); - } else if(message->parameter(1) == "-o") - { - channel->modList.append(message->parameter(2)); - } + auto channel = channelManager.getTwitchChannel(message->parameter(0).remove(0, 1)); + if (message->parameter(1) == "+o") { + channel->modList.append(message->parameter(2)); + } else if (message->parameter(1) == "-o") { + channel->modList.append(message->parameter(2)); + } } bool IrcManager::isTwitchBlockedUser(QString const &username) diff --git a/src/widgets/accountpopup.cpp b/src/widgets/accountpopup.cpp index 49aa6f96..bb0df02e 100644 --- a/src/widgets/accountpopup.cpp +++ b/src/widgets/accountpopup.cpp @@ -1,10 +1,10 @@ #include "widgets/accountpopup.hpp" -#include "util/urlfetch.hpp" #include "accountmanager.hpp" #include "channel.hpp" #include "credentials.hpp" #include "settingsmanager.hpp" #include "ui_accountpopupform.h" +#include "util/urlfetch.hpp" #include #include @@ -32,20 +32,16 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) SettingsManager &settings = SettingsManager::getInstance(); permission = permissions::User; - for(auto button : this->_ui->profileLayout->findChildren()) - { + for (auto button : this->_ui->profileLayout->findChildren()) { button->setFocusProxy(this); } - for(auto button: this->_ui->userLayout->findChildren()) - { + for (auto button : this->_ui->userLayout->findChildren()) { button->setFocusProxy(this); } - for(auto button: this->_ui->modLayout->findChildren()) - { + for (auto button : this->_ui->modLayout->findChildren()) { button->setFocusProxy(this); } - for(auto button: this->_ui->ownerLayout->findChildren()) - { + for (auto button : this->_ui->ownerLayout->findChildren()) { button->setFocusProxy(this); } @@ -69,41 +65,33 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) userNickname = currentTwitchUser->getNickName(); } - QObject::connect(this->_ui->profile, &QPushButton::clicked, this, [=](){ - QDesktopServices::openUrl(QUrl("https://twitch.tv/" + - this->_ui->lblUsername->text())); + QObject::connect(this->_ui->profile, &QPushButton::clicked, this, [=]() { + QDesktopServices::openUrl(QUrl("https://twitch.tv/" + this->_ui->lblUsername->text())); }); - QObject::connect(this->_ui->sendMessage, &QPushButton::clicked, this, [=](){ - QDesktopServices::openUrl(QUrl("https://www.twitch.tv/message/compose?to=" + - this->_ui->lblUsername->text())); + QObject::connect(this->_ui->sendMessage, &QPushButton::clicked, this, [=]() { + QDesktopServices::openUrl( + QUrl("https://www.twitch.tv/message/compose?to=" + this->_ui->lblUsername->text())); }); - QObject::connect(this->_ui->copy, &QPushButton::clicked, this, [=](){ - QApplication::clipboard()->setText(this->_ui->lblUsername->text()); + QObject::connect(this->_ui->copy, &QPushButton::clicked, this, + [=]() { QApplication::clipboard()->setText(this->_ui->lblUsername->text()); }); + + QObject::connect(this->_ui->follow, &QPushButton::clicked, this, [=]() { + QUrl requestUrl("https://api.twitch.tv/kraken/users/" + userId + "/follows/channels/" + + this->userID); + + util::twitch::put(requestUrl, + [](QJsonObject obj) { qDebug() << "follows channel: " << obj; }); }); - QObject::connect(this->_ui->follow, &QPushButton::clicked, this, [=](){ - QUrl requestUrl("https://api.twitch.tv/kraken/users/" + - userId + - "/follows/channels/" + this->userID); + QObject::connect(this->_ui->ignore, &QPushButton::clicked, this, [=]() { + QUrl requestUrl("https://api.twitch.tv/kraken/users/" + userId + "/blocks/" + this->userID); - util::twitch::put(requestUrl,[](QJsonObject obj){ - qDebug() << "follows channel: " << obj; - }); + util::twitch::put(requestUrl, [](QJsonObject obj) { qDebug() << "blocks user: " << obj; }); }); - QObject::connect(this->_ui->ignore, &QPushButton::clicked, this, [=](){ - QUrl requestUrl("https://api.twitch.tv/kraken/users/" + - userId + - "/blocks/" + this->userID); - - util::twitch::put(requestUrl,[](QJsonObject obj){ - qDebug() << "blocks user: " << obj; - }); - }); - - QObject::connect(this->_ui->disableHighlights, &QPushButton::clicked, this, [=, &settings](){ + QObject::connect(this->_ui->disableHighlights, &QPushButton::clicked, this, [=, &settings]() { QString str = settings.highlightUserBlacklist.getnonConst(); str.append(this->_ui->lblUsername->text() + "\n"); settings.highlightUserBlacklist.set(str); @@ -111,7 +99,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) this->_ui->enableHighlights->show(); }); - QObject::connect(this->_ui->enableHighlights, &QPushButton::clicked, this, [=, &settings](){ + QObject::connect(this->_ui->enableHighlights, &QPushButton::clicked, this, [=, &settings]() { QString str = settings.highlightUserBlacklist.getnonConst(); QStringList list = str.split("\n"); list.removeAll(this->_ui->lblUsername->text()); @@ -120,10 +108,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) this->_ui->disableHighlights->show(); }); - - updateButtons(this->_ui->userLayout,false); - updateButtons(this->_ui->modLayout,false); - updateButtons(this->_ui->ownerLayout,false); + updateButtons(this->_ui->userLayout, false); + updateButtons(this->_ui->modLayout, false); + updateButtons(this->_ui->ownerLayout, false); // Close button connect(_ui->btnClose, &QPushButton::clicked, [=]() { @@ -131,9 +118,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) }); util::twitch::getUserID(userNickname, this, - [=](const QString &id){ - currentTwitchUser->setUserId(id); - }); + [=](const QString &id) { currentTwitchUser->setUserId(id); }); } void AccountPopupWidget::setName(const QString &name) @@ -149,7 +134,7 @@ void AccountPopupWidget::setChannel(std::shared_ptr channel) void AccountPopupWidget::getUserId() { - util::twitch::getUserID(this->_ui->lblUsername->text(), this, [=](const QString &id){ + util::twitch::getUserID(this->_ui->lblUsername->text(), this, [=](const QString &id) { userID = id; getUserData(); }); @@ -157,13 +142,14 @@ void AccountPopupWidget::getUserId() void AccountPopupWidget::getUserData() { - util::twitch::get("https://api.twitch.tv/kraken/channels/" + userID, this, [=](const QJsonObject &obj){ - _ui->lblFollowers->setText(QString::number(obj.value("followers").toInt())); - _ui->lblViews->setText(QString::number(obj.value("views").toInt())); - _ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0)); + util::twitch::get( + "https://api.twitch.tv/kraken/channels/" + userID, this, [=](const QJsonObject &obj) { + _ui->lblFollowers->setText(QString::number(obj.value("followers").toInt())); + _ui->lblViews->setText(QString::number(obj.value("views").toInt())); + _ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0)); - loadAvatar(QUrl(obj.value("logo").toString())); - }); + loadAvatar(QUrl(obj.value("logo").toString())); + }); } void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl) @@ -201,36 +187,33 @@ void AccountPopupWidget::updatePermissions() return; } - if(this->_channel.get()->name == currentTwitchUser->getNickName()) - { + if (this->_channel.get()->name == currentTwitchUser->getNickName()) { permission = permissions::Owner; - } - else if(this->_channel->modList.contains(currentTwitchUser->getNickName())) - { + } else if (this->_channel->modList.contains(currentTwitchUser->getNickName())) { // XXX(pajlada): This might always trigger if user is anonymous (if nickName is empty?) permission = permissions::Mod; } } -void AccountPopupWidget::updateButtons(QWidget* layout, bool state) +void AccountPopupWidget::updateButtons(QWidget *layout, bool state) { - for(auto button : layout->findChildren()) - { + for (auto button : layout->findChildren()) { button->setVisible(state); } } void AccountPopupWidget::timeout(QPushButton *button, int time) { - QObject::connect(button, &QPushButton::clicked, this, [=](){ - this->_channel->sendMessage("/timeout " + this->_ui->lblUsername->text() + " " + QString::number(time)); + QObject::connect(button, &QPushButton::clicked, this, [=]() { + this->_channel->sendMessage("/timeout " + this->_ui->lblUsername->text() + " " + + QString::number(time)); }); } void AccountPopupWidget::sendCommand(QPushButton *button, QString command) { - QObject::connect(button, &QPushButton::clicked, this, [=](){ - this->_channel->sendMessage(command + this->_ui->lblUsername->text()); + QObject::connect(button, &QPushButton::clicked, this, [=]() { + this->_channel->sendMessage(command + this->_ui->lblUsername->text()); }); } @@ -253,42 +236,32 @@ void AccountPopupWidget::showEvent(QShowEvent *event) return; } - if(this->_ui->lblUsername->text() != currentTwitchUser->getNickName()) - { + if (this->_ui->lblUsername->text() != currentTwitchUser->getNickName()) { updateButtons(this->_ui->userLayout, true); - if(permission != permissions::User) - { - if(!this->_channel->modList.contains(this->_ui->lblUsername->text())) - { + if (permission != permissions::User) { + if (!this->_channel->modList.contains(this->_ui->lblUsername->text())) { updateButtons(this->_ui->modLayout, true); } - if(permission == permissions::Owner) - { + if (permission == permissions::Owner) { updateButtons(this->_ui->ownerLayout, true); updateButtons(this->_ui->modLayout, true); } } - } - else - { + } else { updateButtons(this->_ui->modLayout, false); updateButtons(this->_ui->userLayout, false); updateButtons(this->_ui->ownerLayout, false); } QString blacklisted = SettingsManager::getInstance().highlightUserBlacklist.getnonConst(); - QStringList list = blacklisted.split("\n",QString::SkipEmptyParts); - if(list.contains(this->_ui->lblUsername->text(),Qt::CaseInsensitive)) - { + QStringList list = blacklisted.split("\n", QString::SkipEmptyParts); + if (list.contains(this->_ui->lblUsername->text(), Qt::CaseInsensitive)) { this->_ui->disableHighlights->hide(); this->_ui->enableHighlights->show(); - } - else - { + } else { this->_ui->disableHighlights->show(); this->_ui->enableHighlights->hide(); } - } } // namespace widgets diff --git a/src/widgets/accountpopup.hpp b/src/widgets/accountpopup.hpp index 8b89ab18..e5eee9cb 100644 --- a/src/widgets/accountpopup.hpp +++ b/src/widgets/accountpopup.hpp @@ -1,4 +1,5 @@ #pragma once + #include "concurrentmap.hpp" #include "twitch/twitchchannel.hpp" @@ -35,9 +36,9 @@ private: void getUserData(); void loadAvatar(const QUrl &avatarUrl); - void updateButtons(QWidget* layout, bool state); - void timeout(QPushButton* button, int time); - void sendCommand(QPushButton* button, QString command); + void updateButtons(QWidget *layout, bool state); + void timeout(QPushButton *button, int time); + void sendCommand(QPushButton *button, QString command); enum class permissions { User, Mod, Owner }; permissions permission; diff --git a/src/widgets/settingsdialog.cpp b/src/widgets/settingsdialog.cpp index b1f1c14f..75e234b6 100644 --- a/src/widgets/settingsdialog.cpp +++ b/src/widgets/settingsdialog.cpp @@ -135,7 +135,8 @@ QVBoxLayout *SettingsDialog::createAccountsTab() // Select the currently logged in user if (listWidget->count() > 0) { - const QString ¤tUsername = AccountManager::getInstance().Twitch.getCurrent()->getUserName(); + const QString ¤tUsername = + AccountManager::getInstance().Twitch.getCurrent()->getUserName(); for (int i = 0; i < listWidget->count(); ++i) { QString itemText = listWidget->item(i)->text(); if (itemText.compare(currentUsername, Qt::CaseInsensitive) == 0) { From bf5e619818c74d010c7b9779f6415555a323c050 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 16:33:46 +0100 Subject: [PATCH 06/15] Remove message-spawning tests --- src/ircmanager.hpp | 3 -- src/widgets/split.cpp | 76 +++---------------------------------------- src/widgets/split.hpp | 3 -- 3 files changed, 4 insertions(+), 78 deletions(-) diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index 87b8de5d..f0f099d0 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -58,11 +58,8 @@ private: std::shared_ptr account = nullptr; std::shared_ptr writeConnection = nullptr; - -public: std::shared_ptr readConnection = nullptr; -private: std::mutex connectionMutex; uint32_t connectionGeneration = 0; diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp index 62707965..252c00e8 100644 --- a/src/widgets/split.cpp +++ b/src/widgets/split.cpp @@ -79,15 +79,10 @@ Split::Split(ChannelManager &_channelManager, SplitContainer *parent) ezShortcut(this, "CTRL+R", &Split::doChangeChannel); // xd - //ezShortcut(this, "ALT+SHIFT+RIGHT", &Split::doIncFlexX); - //ezShortcut(this, "ALT+SHIFT+LEFT", &Split::doDecFlexX); - //ezShortcut(this, "ALT+SHIFT+UP", &Split::doIncFlexY); - //ezShortcut(this, "ALT+SHIFT+DOWN", &Split::doDecFlexY); - -#ifndef NDEBUG - // F12: Toggle message spawning - ezShortcut(this, "ALT+Q", &Split::doToggleMessageSpawning); -#endif + // ezShortcut(this, "ALT+SHIFT+RIGHT", &Split::doIncFlexX); + // ezShortcut(this, "ALT+SHIFT+LEFT", &Split::doDecFlexX); + // ezShortcut(this, "ALT+SHIFT+UP", &Split::doIncFlexY); + // ezShortcut(this, "ALT+SHIFT+DOWN", &Split::doDecFlexY); this->channelName.getValueChangedSignal().connect( std::bind(&Split::channelNameUpdated, this, std::placeholders::_1)); @@ -102,10 +97,6 @@ Split::Split(ChannelManager &_channelManager, SplitContainer *parent) this->input.clearSelection(); } }); - - QTimer *timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &Split::test); - timer->start(1000); } Split::~Split() @@ -462,30 +453,6 @@ void Split::doCopy() QApplication::clipboard()->setText(this->view.getSelectedText()); } -static std::vector usernameVariants = { - "pajlada", // - "trump", // - "Chancu", // - "pajaWoman", // - "fourtf", // - "weneedmoreautisticbots", // - "fourtfbot", // - "pajbot", // - "snusbot", // -}; - -static std::vector messageVariants = { - "hehe", // - "lol pajlada", // - "hehe BANNEDWORD", // - "someone ordered pizza", // - "for ice poseidon", // - "and delivery guy said it is for enza denino", // - "!gn", // - "for my laptop", // - "should I buy a Herschel backpack?", // -}; - template static Iter select_randomly(Iter start, Iter end, RandomGenerator &g) { @@ -502,41 +469,6 @@ static Iter select_randomly(Iter start, Iter end) return select_randomly(start, end, gen); } -void Split::test() -{ - if (this->testEnabled) { - messages::MessageParseArgs args; - - auto message = - new Communi::IrcPrivateMessage(this->channelManager.ircManager.readConnection.get()); - - std::string text = *(select_randomly(messageVariants.begin(), messageVariants.end())); - std::string username = *(select_randomly(usernameVariants.begin(), usernameVariants.end())); - std::string usernameString = username + "!" + username + "@" + username; - - QStringList params{"#pajlada", text.c_str()}; - - qDebug() << params; - - message->setParameters(params); - - message->setPrefix(usernameString.c_str()); - - auto twitchChannel = std::dynamic_pointer_cast(this->channel); - - twitch::TwitchMessageBuilder builder( - twitchChannel.get(), this->channelManager.ircManager.resources, - this->channelManager.emoteManager, this->channelManager.windowManager, message, args); - - twitchChannel->addMessage(builder.parse()); - } -} - -void Split::doToggleMessageSpawning() -{ - this->testEnabled = !this->testEnabled; -} - void Split::doIncFlexX() { this->setFlexSizeX(this->getFlexSizeX() * 1.2); diff --git a/src/widgets/split.hpp b/src/widgets/split.hpp index 8987ce54..31b0eef3 100644 --- a/src/widgets/split.hpp +++ b/src/widgets/split.hpp @@ -52,7 +52,6 @@ public: CompletionManager &completionManager; pajlada::Settings::Setting channelName; boost::signals2::signal channelChanged; - bool testEnabled = false; std::shared_ptr getChannel() const; std::shared_ptr &getChannelRef(); @@ -122,8 +121,6 @@ public slots: // Open viewer list of the channel void doOpenViewerList(); - void doToggleMessageSpawning(); - void test(); void doIncFlexX(); void doDecFlexX(); void doIncFlexY(); From b13b8a2ce6fa4088f59f705493d4902e3e4128d0 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 17:14:38 +0100 Subject: [PATCH 07/15] Simplify IrcManager connection creation/disconnecting reword some comments/add some comments about unfitting methods in IrcManager --- src/ircmanager.cpp | 90 ++++++++++++++++++++++------------------------ src/ircmanager.hpp | 12 +++---- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index fc19cbd4..4c3f7985 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -13,7 +13,6 @@ #include "windowmanager.hpp" #include -#include #include #include #include @@ -41,16 +40,32 @@ IrcManager::IrcManager(ChannelManager &_channelManager, Resources &_resources, postToThread([this] { this->connect(); }); }); + + // Initialize the connections + this->writeConnection.reset(new Communi::IrcConnection); + this->writeConnection->moveToThread(QCoreApplication::instance()->thread()); + + this->readConnection.reset(new Communi::IrcConnection); + this->readConnection->moveToThread(QCoreApplication::instance()->thread()); + + // Listen to read connection message signals + QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived, this, + &IrcManager::messageReceived); + QObject::connect(this->readConnection.get(), &Communi::IrcConnection::privateMessageReceived, + this, &IrcManager::privateMessageReceived); } -void IrcManager::setUser(std::shared_ptr account) +void IrcManager::setUser(std::shared_ptr newAccount) { - this->account = account; + this->account = newAccount; } void IrcManager::connect() { - disconnect(); + this->disconnect(); + + this->initializeConnection(this->writeConnection, false); + this->initializeConnection(this->readConnection, true); // XXX(pajlada): Disabled the async_exec for now, because if we happen to run the // `beginConnecting` function in a different thread than last time, we won't be able to connect @@ -59,17 +74,10 @@ void IrcManager::connect() this->beginConnecting(); } -Communi::IrcConnection *IrcManager::createConnection(bool doRead) +void IrcManager::initializeConnection(const std::unique_ptr &connection, + bool isReadConnection) { assert(this->account); - Communi::IrcConnection *connection = new Communi::IrcConnection; - - if (doRead) { - QObject::connect(connection, &Communi::IrcConnection::messageReceived, this, - &IrcManager::messageReceived); - QObject::connect(connection, &Communi::IrcConnection::privateMessageReceived, this, - &IrcManager::privateMessageReceived); - } QString username = this->account->getUserName(); QString oauthClient = this->account->getOAuthClient(); @@ -88,7 +96,7 @@ Communi::IrcConnection *IrcManager::createConnection(bool doRead) this->refreshIgnoredUsers(username, oauthClient, oauthToken); } - if (doRead) { + if (isReadConnection) { connection->sendCommand( Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership")); connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands")); @@ -97,8 +105,6 @@ Communi::IrcConnection *IrcManager::createConnection(bool doRead) connection->setHost("irc.chat.twitch.tv"); connection->setPort(6667); - - return connection; } void IrcManager::refreshIgnoredUsers(const QString &username, const QString &oauthClient, @@ -139,43 +145,23 @@ void IrcManager::refreshIgnoredUsers(const QString &username, const QString &oau void IrcManager::beginConnecting() { - uint32_t generation = ++this->connectionGeneration; - - Communi::IrcConnection *_writeConnection = this->createConnection(false); - Communi::IrcConnection *_readConnection = this->createConnection(true); - std::lock_guard locker(this->connectionMutex); - if (generation == this->connectionGeneration) { - this->writeConnection = std::shared_ptr(_writeConnection); - this->readConnection = std::shared_ptr(_readConnection); - - this->writeConnection->moveToThread(QCoreApplication::instance()->thread()); - this->readConnection->moveToThread(QCoreApplication::instance()->thread()); - - for (auto &channel : this->channelManager.getItems()) { - this->writeConnection->sendRaw("JOIN #" + channel->name); - this->readConnection->sendRaw("JOIN #" + channel->name); - } - this->writeConnection->open(); - this->readConnection->open(); - } else { - delete _writeConnection; - delete _readConnection; + for (auto &channel : this->channelManager.getItems()) { + this->writeConnection->sendRaw("JOIN #" + channel->name); + this->readConnection->sendRaw("JOIN #" + channel->name); } + + this->writeConnection->open(); + this->readConnection->open(); } void IrcManager::disconnect() { - this->connectionMutex.lock(); + std::lock_guard locker(this->connectionMutex); - auto _readConnection = this->readConnection; - auto _writeConnection = this->writeConnection; - - this->readConnection.reset(); - this->writeConnection.reset(); - - this->connectionMutex.unlock(); + this->readConnection->close(); + this->writeConnection->close(); } void IrcManager::sendMessage(const QString &channelName, const QString &message) @@ -272,17 +258,17 @@ void IrcManager::handleRoomStateMessage(Communi::IrcMessage *message) void IrcManager::handleClearChatMessage(Communi::IrcMessage *message) { - // do nothing + // TODO: Implement } void IrcManager::handleUserStateMessage(Communi::IrcMessage *message) { - // do nothing + // TODO: Implement } void IrcManager::handleWhisperMessage(Communi::IrcMessage *message) { - // do nothing + // TODO: Implement } void IrcManager::handleUserNoticeMessage(Communi::IrcMessage *message) @@ -300,6 +286,7 @@ void IrcManager::handleModeMessage(Communi::IrcMessage *message) } } +// XXX: This does not fit in IrcManager bool IrcManager::isTwitchBlockedUser(QString const &username) { QMutexLocker locker(&this->twitchBlockedUsersMutex); @@ -309,6 +296,7 @@ bool IrcManager::isTwitchBlockedUser(QString const &username) return iterator != this->twitchBlockedUsers.end(); } +// XXX: This does not fit in IrcManager bool IrcManager::tryAddIgnoredUser(QString const &username, QString &errorMessage) { assert(this->account); @@ -332,9 +320,11 @@ bool IrcManager::tryAddIgnoredUser(QString const &username, QString &errorMessag reply->deleteLater(); errorMessage = "Error while ignoring user \"" + username + "\": " + reply->errorString(); + return false; } +// XXX: This does not fit in IrcManager void IrcManager::addIgnoredUser(QString const &username) { QString errorMessage; @@ -343,9 +333,11 @@ void IrcManager::addIgnoredUser(QString const &username) } } +// XXX: This does not fit in IrcManager bool IrcManager::tryRemoveIgnoredUser(QString const &username, QString &errorMessage) { assert(this->account); + QUrl url("https://api.twitch.tv/kraken/users/" + this->account->getUserName() + "/blocks/" + username + "?oauth_token=" + this->account->getOAuthToken() + "&client_id=" + this->account->getOAuthClient()); @@ -365,9 +357,11 @@ bool IrcManager::tryRemoveIgnoredUser(QString const &username, QString &errorMes reply->deleteLater(); errorMessage = "Error while unignoring user \"" + username + "\": " + reply->errorString(); + return false; } +// XXX: This does not fit in IrcManager void IrcManager::removeIgnoredUser(QString const &username) { QString errorMessage; diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index f0f099d0..6bdbe4fc 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -5,6 +5,7 @@ #include "messages/message.hpp" #include "twitch/twitchuser.hpp" +#include #include #include #include @@ -44,7 +45,7 @@ public: void joinChannel(const QString &channelName); void partChannel(const QString &channelName); - void setUser(std::shared_ptr account); + void setUser(std::shared_ptr newAccount); pajlada::Signals::Signal onPrivateMessage; @@ -57,19 +58,18 @@ private: // variables std::shared_ptr account = nullptr; - std::shared_ptr writeConnection = nullptr; - std::shared_ptr readConnection = nullptr; + std::unique_ptr writeConnection = nullptr; + std::unique_ptr readConnection = nullptr; std::mutex connectionMutex; - uint32_t connectionGeneration = 0; QMap twitchBlockedUsers; QMutex twitchBlockedUsersMutex; QNetworkAccessManager networkAccessManager; - // methods - Communi::IrcConnection *createConnection(bool doRead); + void initializeConnection(const std::unique_ptr &connection, + bool isReadConnection); void refreshIgnoredUsers(const QString &username, const QString &oauthClient, const QString &oauthToken); From 3cc19bd4ce99f0d7260c089534971b83d783ae90 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 17:53:48 +0100 Subject: [PATCH 08/15] reformat messagecolor class --- src/messages/messagecolor.cpp | 6 ++++-- src/messages/messagecolor.hpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/messages/messagecolor.cpp b/src/messages/messagecolor.cpp index 387c1da6..135d0298 100644 --- a/src/messages/messagecolor.cpp +++ b/src/messages/messagecolor.cpp @@ -2,6 +2,7 @@ namespace chatterino { namespace messages { + MessageColor::MessageColor(const QColor &_color) : type(Type::Custom) , color(_color) @@ -34,5 +35,6 @@ const QColor &MessageColor::getColor(ColorScheme &colorScheme) const static QColor _default; return _default; } -} -} + +} // namespace messages +} // namespace chatterino diff --git a/src/messages/messagecolor.hpp b/src/messages/messagecolor.hpp index 3ea07886..48079f9c 100644 --- a/src/messages/messagecolor.hpp +++ b/src/messages/messagecolor.hpp @@ -6,6 +6,7 @@ namespace chatterino { namespace messages { + class MessageColor { public: @@ -21,5 +22,6 @@ private: Type type; QColor color; }; -} -} + +} // namespace messages +} // namespace chatterino From 87203c11203e9a80f75907b22c6858d3ec5bdc14 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 18:11:36 +0100 Subject: [PATCH 09/15] Add system messages upon connecting/disconnecting --- src/channelmanager.cpp | 10 ++++++++++ src/channelmanager.hpp | 2 ++ src/ircmanager.cpp | 25 +++++++++++++++++++++++++ src/ircmanager.hpp | 3 +++ src/messages/message.cpp | 28 ++++++++++++++++++++++++++++ src/messages/message.hpp | 2 ++ 6 files changed, 70 insertions(+) diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp index 7f26232c..9e9cdd2c 100644 --- a/src/channelmanager.cpp +++ b/src/channelmanager.cpp @@ -136,4 +136,14 @@ WindowManager &ChannelManager::getWindowManager() return this->windowManager; } +void ChannelManager::doOnAll(std::function)> func) +{ + for (const auto &channel : this->twitchChannels) { + func(std::get<0>(channel)); + } + + func(this->whispersChannel); + func(this->mentionsChannel); +} + } // namespace chatterino diff --git a/src/channelmanager.hpp b/src/channelmanager.hpp index a4cd71b3..77a5712a 100644 --- a/src/channelmanager.hpp +++ b/src/channelmanager.hpp @@ -32,6 +32,8 @@ public: EmoteManager &getEmoteManager(); WindowManager &getWindowManager(); + void doOnAll(std::function)> func); + // Special channels const std::shared_ptr whispersChannel; const std::shared_ptr mentionsChannel; diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 4c3f7985..f145dc01 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -53,6 +53,11 @@ IrcManager::IrcManager(ChannelManager &_channelManager, Resources &_resources, &IrcManager::messageReceived); QObject::connect(this->readConnection.get(), &Communi::IrcConnection::privateMessageReceived, this, &IrcManager::privateMessageReceived); + + QObject::connect(this->readConnection.get(), &Communi::IrcConnection::connected, this, + &IrcManager::onConnected); + QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected, this, + &IrcManager::onDisconnected); } void IrcManager::setUser(std::shared_ptr newAccount) @@ -370,4 +375,24 @@ void IrcManager::removeIgnoredUser(QString const &username) } } +void IrcManager::onConnected() +{ + std::shared_ptr msg(Message::createSystemMessage("connected to chat")); + + this->channelManager.doOnAll([msg](std::shared_ptr channel) { + assert(channel); + channel->addMessage(msg); + }); +} + +void IrcManager::onDisconnected() +{ + std::shared_ptr msg(Message::createSystemMessage("disconnected from chat")); + + this->channelManager.doOnAll([msg](std::shared_ptr channel) { + assert(channel); + channel->addMessage(msg); + }); +} + } // namespace chatterino diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index 6bdbe4fc..c64c98a4 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -85,6 +85,9 @@ private: void handleWhisperMessage(Communi::IrcMessage *message); void handleUserNoticeMessage(Communi::IrcMessage *message); void handleModeMessage(Communi::IrcMessage *message); + + void onConnected(); + void onDisconnected(); }; } // namespace chatterino diff --git a/src/messages/message.cpp b/src/messages/message.cpp index 0635e863..2d15e651 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -70,5 +70,33 @@ const QString &Message::getId() const return this->id; } +/// Static +Message *Message::createSystemMessage(const QString &text) +{ + Message *message = new Message; + + std::time_t t; + time(&t); + char timeStampBuffer[69]; + + // Add word for timestamp with no seconds + strftime(timeStampBuffer, 69, "%H:%M", localtime(&t)); + QString timestampNoSeconds(timeStampBuffer); + message->getWords().push_back(Word(timestampNoSeconds, Word::TimestampNoSeconds, + MessageColor(MessageColor::System), QString(), QString())); + + // Add word for timestamp with seconds + strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&t)); + QString timestampWithSeconds(timeStampBuffer); + message->getWords().push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds, + MessageColor(MessageColor::System), QString(), QString())); + + Word word(text, Word::Type::Default, MessageColor(MessageColor::Type::System), text, text); + + message->getWords().push_back(word); + + return message; +} + } // namespace messages } // namespace chatterino diff --git a/src/messages/message.hpp b/src/messages/message.hpp index 349ee3cb..fded9131 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -33,6 +33,8 @@ public: const QString text; bool centered = false; + static Message *createSystemMessage(const QString &text); + private: static LazyLoadedImage *badgeStaff; static LazyLoadedImage *badgeAdmin; From b39034ab74e1fda5edae1fe009f3077da1cf256a Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 19:03:22 +0100 Subject: [PATCH 10/15] Move message timestamp-code to its own function --- src/messages/message.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/messages/message.cpp b/src/messages/message.cpp index 2d15e651..e0469b9f 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -70,11 +70,10 @@ const QString &Message::getId() const return this->id; } -/// Static -Message *Message::createSystemMessage(const QString &text) -{ - Message *message = new Message; +namespace { +void AddCurrentTimestamp(Message *message) +{ std::time_t t; time(&t); char timeStampBuffer[69]; @@ -90,6 +89,23 @@ Message *Message::createSystemMessage(const QString &text) QString timestampWithSeconds(timeStampBuffer); message->getWords().push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds, MessageColor(MessageColor::System), QString(), QString())); +} + +} // namespace + +/// Static +Message *Message::createSystemMessage(const QString &text) +{ + Message *message = new Message; + + AddCurrentTimestamp(message); + + Word word(text, Word::Type::Default, MessageColor(MessageColor::Type::System), text, text); + + message->getWords().push_back(word); + + return message; +} Word word(text, Word::Type::Default, MessageColor(MessageColor::Type::System), text, text); From 6d56148ed260f1e78ec98d13bb7efe6ca45a35f1 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 19:08:32 +0100 Subject: [PATCH 11/15] Implement basic ClearChat handling Fixes #56 --- src/ircmanager.cpp | 44 +++++++++++++++++++++++++++++++++++++++- src/messages/message.cpp | 30 +++++++++++++++++++++++++++ src/messages/message.hpp | 3 +++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index f145dc01..8723ea82 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -263,7 +263,49 @@ void IrcManager::handleRoomStateMessage(Communi::IrcMessage *message) void IrcManager::handleClearChatMessage(Communi::IrcMessage *message) { - // TODO: Implement + assert(message->parameters().length() >= 1); + + auto rawChannelName = message->parameter(0); + + assert(rawChannelName.length() >= 2); + + auto trimmedChannelName = rawChannelName.mid(1); + + auto c = this->channelManager.getTwitchChannel(trimmedChannelName); + + if (!c) { + debug::Log("[IrcManager:handleClearChatMessage] Channel {} not found in channel manager", + trimmedChannelName); + return; + } + + if (message->parameters().length() == 1) { + std::shared_ptr msg( + Message::createSystemMessage("Chat has been cleared by a moderator.")); + + c->addMessage(msg); + + return; + } + + assert(message->parameters().length() >= 2); + + QString username = message->parameter(1); + QString durationInSeconds, reason; + QVariant v = message->tag("ban-duration"); + if (v.isValid()) { + durationInSeconds = v.toString(); + } + + v = message->tag("ban-reason"); + if (v.isValid()) { + reason = v.toString(); + } + + std::shared_ptr msg( + Message::createTimeoutMessage(username, durationInSeconds, reason)); + + c->addMessage(msg); } void IrcManager::handleUserStateMessage(Communi::IrcMessage *message) diff --git a/src/messages/message.cpp b/src/messages/message.cpp index e0469b9f..dc456728 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -107,6 +107,36 @@ Message *Message::createSystemMessage(const QString &text) return message; } +Message *Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds, + const QString &reason) +{ + Message *message = new Message; + + AddCurrentTimestamp(message); + + QString text; + + text.append(username); + text.append(" has been timed out"); + + // TODO: Implement who timed the user out + + text.append(" for "); + text.append(durationInSeconds); + bool ok = true; + int timeoutDuration = durationInSeconds.toInt(&ok); + text.append(" second"); + if (ok && timeoutDuration > 1) { + text.append("s"); + } + + if (reason.length() > 0) { + text.append(": \""); + text.append(reason); + text.append("\""); + } + text.append("."); + Word word(text, Word::Type::Default, MessageColor(MessageColor::Type::System), text, text); message->getWords().push_back(word); diff --git a/src/messages/message.hpp b/src/messages/message.hpp index fded9131..cb49366f 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -35,6 +35,9 @@ public: static Message *createSystemMessage(const QString &text); + static Message *createTimeoutMessage(const QString &username, const QString &durationInSeconds, + const QString &reason); + private: static LazyLoadedImage *badgeStaff; static LazyLoadedImage *badgeAdmin; From d905e8867128e369a7325928501912cf8396697e Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 19:20:57 +0100 Subject: [PATCH 12/15] We now handle irc NOTICE messages --- src/ircmanager.cpp | 23 +++++++++++++++++++++++ src/ircmanager.hpp | 1 + 2 files changed, 24 insertions(+) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 8723ea82..30d53e62 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -242,6 +242,8 @@ void IrcManager::messageReceived(Communi::IrcMessage *message) this->handleUserNoticeMessage(message); } else if (command == "MODE") { this->handleModeMessage(message); + } else if (command == "NOTICE") { + this->handleNoticeMessage(static_cast(message)); } } @@ -417,6 +419,27 @@ void IrcManager::removeIgnoredUser(QString const &username) } } +void IrcManager::handleNoticeMessage(Communi::IrcNoticeMessage *message) +{ + auto rawChannelName = message->target(); + + assert(rawChannelName.length() >= 2); + + auto trimmedChannelName = rawChannelName.mid(1); + + auto c = this->channelManager.getTwitchChannel(trimmedChannelName); + + if (!c) { + debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager", + trimmedChannelName); + return; + } + + std::shared_ptr msg(Message::createSystemMessage(message->content())); + + c->addMessage(msg); +} + void IrcManager::onConnected() { std::shared_ptr msg(Message::createSystemMessage("connected to chat")); diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index c64c98a4..ca247c9f 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -85,6 +85,7 @@ private: void handleWhisperMessage(Communi::IrcMessage *message); void handleUserNoticeMessage(Communi::IrcMessage *message); void handleModeMessage(Communi::IrcMessage *message); + void handleNoticeMessage(Communi::IrcNoticeMessage *message); void onConnected(); void onDisconnected(); From 357515ab397df605c12dc98af29511bf69792b2c Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 19:45:23 +0100 Subject: [PATCH 13/15] createTimeoutMessage now also handles permaban-messages --- src/messages/message.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/messages/message.cpp b/src/messages/message.cpp index dc456728..e58eef6e 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -117,17 +117,21 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d QString text; text.append(username); - text.append(" has been timed out"); + if (!durationInSeconds.isEmpty()) { + text.append(" has been timed out"); - // TODO: Implement who timed the user out + // TODO: Implement who timed the user out - text.append(" for "); - text.append(durationInSeconds); - bool ok = true; - int timeoutDuration = durationInSeconds.toInt(&ok); - text.append(" second"); - if (ok && timeoutDuration > 1) { - text.append("s"); + text.append(" for "); + text.append(durationInSeconds); + bool ok = true; + int timeoutDuration = durationInSeconds.toInt(&ok); + text.append(" second"); + if (ok && timeoutDuration > 1) { + text.append("s"); + } + } else { + text.append(" has been permanently banned"); } if (reason.length() > 0) { From 3cfb00d61fc9cbf8714a23df4d6694c71cc3109f Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 16 Dec 2017 19:46:27 +0100 Subject: [PATCH 14/15] We now handle some write-connection messages like if users have been banned/timed out successfully by the user, or unbanned etc --- src/ircmanager.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++ src/ircmanager.hpp | 3 +++ 2 files changed, 56 insertions(+) diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 30d53e62..1e2bd605 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -45,6 +45,9 @@ IrcManager::IrcManager(ChannelManager &_channelManager, Resources &_resources, this->writeConnection.reset(new Communi::IrcConnection); this->writeConnection->moveToThread(QCoreApplication::instance()->thread()); + QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived, this, + &IrcManager::writeConnectionMessageReceived); + this->readConnection.reset(new Communi::IrcConnection); this->readConnection->moveToThread(QCoreApplication::instance()->thread()); @@ -106,6 +109,12 @@ void IrcManager::initializeConnection(const std::unique_ptrsendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands")); connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags")); + } else { + connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags")); + + connection->sendCommand( + Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership")); + connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands")); } connection->setHost("irc.chat.twitch.tv"); @@ -247,6 +256,16 @@ void IrcManager::messageReceived(Communi::IrcMessage *message) } } +void IrcManager::writeConnectionMessageReceived(Communi::IrcMessage *message) +{ + switch (message->type()) { + case Communi::IrcMessage::Type::Notice: { + this->handleWriteConnectionNoticeMessage( + static_cast(message)); + } break; + } +} + void IrcManager::handleRoomStateMessage(Communi::IrcMessage *message) { const auto &tags = message->tags(); @@ -440,6 +459,40 @@ void IrcManager::handleNoticeMessage(Communi::IrcNoticeMessage *message) c->addMessage(msg); } +void IrcManager::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message) +{ + auto rawChannelName = message->target(); + + assert(rawChannelName.length() >= 2); + + auto trimmedChannelName = rawChannelName.mid(1); + + auto c = this->channelManager.getTwitchChannel(trimmedChannelName); + + if (!c) { + debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager", + trimmedChannelName); + return; + } + + QVariant v = message->tag("msg-id"); + if (!v.isValid()) { + return; + } + QString msg_id = v.toString(); + + static QList idsToSkip = {"timeout_success", "ban_success"}; + + if (idsToSkip.contains(msg_id)) { + // Already handled in the read-connection + return; + } + + std::shared_ptr msg(Message::createSystemMessage(message->content())); + + c->addMessage(msg); +} + void IrcManager::onConnected() { std::shared_ptr msg(Message::createSystemMessage("connected to chat")); diff --git a/src/ircmanager.hpp b/src/ircmanager.hpp index ca247c9f..ac143a04 100644 --- a/src/ircmanager.hpp +++ b/src/ircmanager.hpp @@ -79,6 +79,8 @@ private: void privateMessageReceived(Communi::IrcPrivateMessage *message); void messageReceived(Communi::IrcMessage *message); + void writeConnectionMessageReceived(Communi::IrcMessage *message); + void handleRoomStateMessage(Communi::IrcMessage *message); void handleClearChatMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message); @@ -86,6 +88,7 @@ private: void handleUserNoticeMessage(Communi::IrcMessage *message); void handleModeMessage(Communi::IrcMessage *message); void handleNoticeMessage(Communi::IrcNoticeMessage *message); + void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message); void onConnected(); void onDisconnected(); From e060f87b3c491ee4434a2dcf843be84dcc84d342 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sun, 17 Dec 2017 00:01:42 +0100 Subject: [PATCH 15/15] hehe fourtf --- chatterino.pro | 3 ++- src/messages/message.cpp | 3 ++- src/util/irchelpers.hpp | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/util/irchelpers.hpp diff --git a/chatterino.pro b/chatterino.pro index 5f3b4870..3b704d19 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -169,7 +169,8 @@ HEADERS += \ src/widgets/window.hpp \ src/widgets/splitcontainer.hpp \ src/widgets/helper/droppreview.hpp \ - src/widgets/helper/splitcolumn.hpp + src/widgets/helper/splitcolumn.hpp \ + src/util/irchelpers.hpp PRECOMPILED_HEADER = diff --git a/src/messages/message.cpp b/src/messages/message.cpp index e58eef6e..0e675b1b 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -7,6 +7,7 @@ #include "ircmanager.hpp" #include "messages/link.hpp" #include "resources.hpp" +#include "util/irchelpers.hpp" #include #include @@ -136,7 +137,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d if (reason.length() > 0) { text.append(": \""); - text.append(reason); + text.append(ParseTagString(reason)); text.append("\""); } text.append("."); diff --git a/src/util/irchelpers.hpp b/src/util/irchelpers.hpp new file mode 100644 index 00000000..c8ba2848 --- /dev/null +++ b/src/util/irchelpers.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace chatterino { + +QString ParseTagString(const QString &input) +{ + QString output = input; + + // code goes here + + return output; +} + +} // namespace chatterino