From fd4967a8958a7ebe658f47f6a593f1a8fa9ce2bd Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 12:20:04 +0200 Subject: [PATCH 1/6] Make TwitchIrcServer an entire IrcServer(?) by itself It no longer relies on AbstractIrcServer & IAbstractIrcServer --- mocks/include/mocks/EmptyApplication.hpp | 2 +- mocks/include/mocks/TwitchIrcServer.hpp | 30 ++ src/Application.cpp | 3 +- src/Application.hpp | 6 +- src/providers/twitch/IrcMessageHandler.cpp | 17 +- src/providers/twitch/IrcMessageHandler.hpp | 7 +- src/providers/twitch/TwitchAccount.cpp | 1 - src/providers/twitch/TwitchIrcServer.cpp | 367 +++++++++++++++++++-- src/providers/twitch/TwitchIrcServer.hpp | 92 +++++- 9 files changed, 472 insertions(+), 53 deletions(-) diff --git a/mocks/include/mocks/EmptyApplication.hpp b/mocks/include/mocks/EmptyApplication.hpp index c8890287137..1cf368154bb 100644 --- a/mocks/include/mocks/EmptyApplication.hpp +++ b/mocks/include/mocks/EmptyApplication.hpp @@ -133,7 +133,7 @@ class EmptyApplication : public IApplication return nullptr; } - IAbstractIrcServer *getTwitchAbstract() override + ITwitchIrcServer *getTwitchAbstract() override { assert(false && "EmptyApplication::getTwitchAbstract was called " "without being initialized"); diff --git a/mocks/include/mocks/TwitchIrcServer.hpp b/mocks/include/mocks/TwitchIrcServer.hpp index 9428a1d1dc3..97cb079add5 100644 --- a/mocks/include/mocks/TwitchIrcServer.hpp +++ b/mocks/include/mocks/TwitchIrcServer.hpp @@ -26,6 +26,36 @@ class MockTwitchIrcServer : public ITwitchIrcServer { } + void connect() override + { + } + + void sendRawMessage(const QString &rawMessage) override + { + } + + ChannelPtr getOrAddChannel(const QString &dirtyChannelName) override + { + assert(false && "unimplemented getOrAddChannel in mock irc server"); + } + + ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) override + { + assert(false && "unimplemented getChannelOrEmpty in mock irc server"); + } + + void addFakeMessage(const QString &data) override + { + } + + void addGlobalSystemMessage(const QString &messageText) override + { + } + + void forEachChannel(std::function func) override + { + } + void forEachChannelAndSpecialChannels( std::function func) override { diff --git a/src/Application.cpp b/src/Application.cpp index d213f16fa08..b58c6d14761 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -14,7 +14,6 @@ #include "controllers/sound/ISoundController.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp" -#include "providers/irc/AbstractIrcServer.hpp" #include "providers/links/LinkResolver.hpp" #include "providers/seventv/SeventvAPI.hpp" #include "providers/seventv/SeventvEmotes.hpp" @@ -546,7 +545,7 @@ ITwitchIrcServer *Application::getTwitch() return this->twitch.get(); } -IAbstractIrcServer *Application::getTwitchAbstract() +ITwitchIrcServer *Application::getTwitchAbstract() { assertInGuiThread(); diff --git a/src/Application.hpp b/src/Application.hpp index 09dc38e2f59..7eb7abda8f7 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -55,7 +55,6 @@ class SeventvEmotes; class SeventvEventAPI; class ILinkResolver; class IStreamerMode; -class IAbstractIrcServer; class IApplication { @@ -84,7 +83,7 @@ class IApplication virtual HighlightController *getHighlights() = 0; virtual NotificationController *getNotifications() = 0; virtual ITwitchIrcServer *getTwitch() = 0; - virtual IAbstractIrcServer *getTwitchAbstract() = 0; + virtual ITwitchIrcServer *getTwitchAbstract() = 0; virtual PubSub *getTwitchPubSub() = 0; virtual ILogging *getChatLogger() = 0; virtual IChatterinoBadges *getChatterinoBadges() = 0; @@ -195,7 +194,8 @@ class Application : public IApplication NotificationController *getNotifications() override; HighlightController *getHighlights() override; ITwitchIrcServer *getTwitch() override; - IAbstractIrcServer *getTwitchAbstract() override; + [[deprecated("use getTwitch()")]] ITwitchIrcServer *getTwitchAbstract() + override; PubSub *getTwitchPubSub() override; ILogging *getChatLogger() override; FfzBadges *getFfzBadges() override; diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 7660cd1158b..d0b284bb24d 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -14,7 +14,6 @@ #include "messages/MessageColor.hpp" #include "messages/MessageElement.hpp" #include "messages/MessageThread.hpp" -#include "providers/irc/AbstractIrcServer.hpp" #include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchAccountManager.hpp" @@ -171,7 +170,7 @@ void updateReplyParticipatedStatus(const QVariantMap &tags, } ChannelPtr channelOrEmptyByTarget(const QString &target, - IAbstractIrcServer &server) + ITwitchIrcServer &server) { QString channelName; if (!trimChannelName(target, channelName)) @@ -679,10 +678,9 @@ std::vector IrcMessageHandler::parseMessageWithReply( } void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, - ITwitchIrcServer &twitchServer, - IAbstractIrcServer &abstractIrcServer) + ITwitchIrcServer &twitchServer) { - auto chan = channelOrEmptyByTarget(message->target(), abstractIrcServer); + auto chan = channelOrEmptyByTarget(message->target(), twitchServer); if (chan->isEmpty()) { return; @@ -988,9 +986,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *ircMessage) } } -void IrcMessageHandler::handleUserNoticeMessage( - Communi::IrcMessage *message, ITwitchIrcServer &twitchServer, - IAbstractIrcServer &abstractIrcServer) +void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, + ITwitchIrcServer &twitchServer) { auto tags = message->tags(); auto parameters = message->parameters(); @@ -1003,7 +1000,7 @@ void IrcMessageHandler::handleUserNoticeMessage( content = parameters[1]; } - auto chn = abstractIrcServer.getChannelOrEmpty(target); + auto chn = twitchServer.getChannelOrEmpty(target); if (isIgnoredMessage({ .message = content, .twitchUserID = tags.value("user-id").toString(), @@ -1096,7 +1093,7 @@ void IrcMessageHandler::handleUserNoticeMessage( return; } - auto chan = abstractIrcServer.getChannelOrEmpty(channelName); + auto chan = twitchServer.getChannelOrEmpty(channelName); if (!chan->isEmpty()) { diff --git a/src/providers/twitch/IrcMessageHandler.hpp b/src/providers/twitch/IrcMessageHandler.hpp index 60dbacaf356..705f39ba383 100644 --- a/src/providers/twitch/IrcMessageHandler.hpp +++ b/src/providers/twitch/IrcMessageHandler.hpp @@ -9,7 +9,6 @@ namespace chatterino { -class IAbstractIrcServer; class ITwitchIrcServer; class Channel; using ChannelPtr = std::shared_ptr; @@ -39,8 +38,7 @@ class IrcMessageHandler std::vector &otherLoaded); void handlePrivMessage(Communi::IrcPrivateMessage *message, - ITwitchIrcServer &twitchServer, - IAbstractIrcServer &abstractIrcServer); + ITwitchIrcServer &twitchServer); void handleRoomStateMessage(Communi::IrcMessage *message); void handleClearChatMessage(Communi::IrcMessage *message); @@ -50,8 +48,7 @@ class IrcMessageHandler void handleWhisperMessage(Communi::IrcMessage *ircMessage); void handleUserNoticeMessage(Communi::IrcMessage *message, - ITwitchIrcServer &twitchServer, - IAbstractIrcServer &abstractIrcServer); + ITwitchIrcServer &twitchServer); void handleNoticeMessage(Communi::IrcNoticeMessage *message); diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp index 56cb52240d5..363d313ec07 100644 --- a/src/providers/twitch/TwitchAccount.cpp +++ b/src/providers/twitch/TwitchAccount.cpp @@ -9,7 +9,6 @@ #include "debug/AssertInGuiThread.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" -#include "providers/irc/IrcMessageBuilder.hpp" #include "providers/IvrApi.hpp" #include "providers/seventv/SeventvAPI.hpp" #include "providers/twitch/api/Helix.hpp" diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp index 0fdc5bf1a7d..583bd810703 100644 --- a/src/providers/twitch/TwitchIrcServer.cpp +++ b/src/providers/twitch/TwitchIrcServer.cpp @@ -2,35 +2,45 @@ #include "Application.hpp" #include "common/Channel.hpp" +#include "common/Common.hpp" #include "common/Env.hpp" #include "common/QLogging.hpp" #include "controllers/accounts/AccountController.hpp" +#include "messages/LimitedQueueSnapshot.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" #include "providers/bttv/BttvEmotes.hpp" -#include "providers/bttv/BttvLiveUpdates.hpp" #include "providers/ffz/FfzEmotes.hpp" -#include "providers/seventv/eventapi/Subscription.hpp" +#include "providers/irc/IrcConnection2.hpp" #include "providers/seventv/SeventvEmotes.hpp" #include "providers/seventv/SeventvEventAPI.hpp" #include "providers/twitch/api/Helix.hpp" -#include "providers/twitch/ChannelPointReward.hpp" #include "providers/twitch/IrcMessageHandler.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "singletons/Settings.hpp" -#include "util/Helpers.hpp" #include "util/PostToThread.hpp" +#include "util/RatelimitBucket.hpp" #include +#include +#include +#include +#include #include #include +#include +#include using namespace std::chrono_literals; namespace { +// Ratelimits for joinBucket_ +constexpr int JOIN_RATELIMIT_BUDGET = 18; +constexpr int JOIN_RATELIMIT_COOLDOWN = 12500; + using namespace chatterino; void sendHelixMessage(const std::shared_ptr &channel, @@ -140,12 +150,77 @@ TwitchIrcServer::TwitchIrcServer() , automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod)) , watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching) { - this->initializeIrc(); + // Initialize the connections + // XXX: don't create write connection if there is no separate write connection. + this->writeConnection_.reset(new IrcConnection); + this->writeConnection_->moveToThread( + QCoreApplication::instance()->thread()); + + // Apply a leaky bucket rate limiting to JOIN messages + auto actuallyJoin = [&](QString message) { + if (!this->channels.contains(message)) + { + return; + } + this->readConnection_->sendRaw("JOIN #" + message); + }; + this->joinBucket_.reset(new RatelimitBucket( + JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this)); + + QObject::connect(this->writeConnection_.get(), + &Communi::IrcConnection::messageReceived, this, + [this](auto msg) { + this->writeConnectionMessageReceived(msg); + }); + QObject::connect(this->writeConnection_.get(), + &Communi::IrcConnection::connected, this, [this] { + this->onWriteConnected(this->writeConnection_.get()); + }); + this->connections_.managedConnect( + this->writeConnection_->connectionLost, [this](bool timeout) { + qCDebug(chatterinoIrc) + << "Write connection reconnect requested. Timeout:" << timeout; + this->writeConnection_->smartReconnect(); + }); - // getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) { - // this->connect(); }, - // this->signalHolder_, - // false); + // Listen to read connection message signals + this->readConnection_.reset(new IrcConnection); + this->readConnection_->moveToThread(QCoreApplication::instance()->thread()); + + QObject::connect(this->readConnection_.get(), + &Communi::IrcConnection::messageReceived, this, + [this](auto msg) { + this->readConnectionMessageReceived(msg); + }); + QObject::connect(this->readConnection_.get(), + &Communi::IrcConnection::privateMessageReceived, this, + [this](auto msg) { + this->privateMessageReceived(msg); + }); + QObject::connect(this->readConnection_.get(), + &Communi::IrcConnection::connected, this, [this] { + this->onReadConnected(this->readConnection_.get()); + }); + QObject::connect(this->readConnection_.get(), + &Communi::IrcConnection::disconnected, this, [this] { + this->onDisconnected(); + }); + this->connections_.managedConnect( + this->readConnection_->connectionLost, [this](bool timeout) { + qCDebug(chatterinoIrc) + << "Read connection reconnect requested. Timeout:" << timeout; + if (timeout) + { + // Show additional message since this is going to interrupt a + // connection that is still "connected" + this->addGlobalSystemMessage( + "Server connection timed out, reconnecting"); + } + this->readConnection_->smartReconnect(); + }); + this->connections_.managedConnect(this->readConnection_->heartbeat, [this] { + this->markChannelsConnected(); + }); } void TwitchIrcServer::initialize() @@ -246,14 +321,12 @@ std::shared_ptr TwitchIrcServer::createChannel( void TwitchIrcServer::privateMessageReceived( Communi::IrcPrivateMessage *message) { - IrcMessageHandler::instance().handlePrivMessage(message, *this, *this); + IrcMessageHandler::instance().handlePrivMessage(message, *this); } void TwitchIrcServer::readConnectionMessageReceived( Communi::IrcMessage *message) { - AbstractIrcServer::readConnectionMessageReceived(message); - if (message->type() == Communi::IrcMessage::Type::Private) { // We already have a handler for private messages @@ -293,7 +366,7 @@ void TwitchIrcServer::readConnectionMessageReceived( } else if (command == "USERNOTICE") { - handler.handleUserNoticeMessage(message, *this, *this); + handler.handleUserNoticeMessage(message, *this); } else if (command == "NOTICE") { @@ -344,6 +417,84 @@ void TwitchIrcServer::writeConnectionMessageReceived( } } +void TwitchIrcServer::onReadConnected(IrcConnection *connection) +{ + (void)connection; + + std::lock_guard lock(this->channelMutex); + + // join channels + for (auto &&weak : this->channels) + { + if (auto channel = weak.lock()) + { + this->joinBucket_->send(channel->getName()); + } + } + + // connected/disconnected message + auto connectedMsg = makeSystemMessage("connected"); + connectedMsg->flags.set(MessageFlag::ConnectedMessage); + auto reconnected = makeSystemMessage("reconnected"); + reconnected->flags.set(MessageFlag::ConnectedMessage); + + for (std::weak_ptr &weak : this->channels.values()) + { + std::shared_ptr chan = weak.lock(); + if (!chan) + { + continue; + } + + LimitedQueueSnapshot snapshot = chan->getMessageSnapshot(); + + bool replaceMessage = + snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has( + MessageFlag::DisconnectedMessage); + + if (replaceMessage) + { + chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected); + } + else + { + chan->addMessage(connectedMsg, MessageContext::Original); + } + } + + this->falloffCounter_ = 1; +} + +void TwitchIrcServer::onWriteConnected(IrcConnection *connection) +{ + (void)connection; +} + +void TwitchIrcServer::onDisconnected() +{ + std::lock_guard lock(this->channelMutex); + + MessageBuilder b(systemMessage, "disconnected"); + b->flags.set(MessageFlag::DisconnectedMessage); + auto disconnectedMsg = b.release(); + + for (std::weak_ptr &weak : this->channels.values()) + { + std::shared_ptr chan = weak.lock(); + if (!chan) + { + continue; + } + + chan->addMessage(disconnectedMsg, MessageContext::Original); + + if (auto *channel = dynamic_cast(chan.get())) + { + channel->markDisconnected(); + } + } +} + std::shared_ptr TwitchIrcServer::getCustomChannel( const QString &channelName) { @@ -521,12 +672,6 @@ QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName) } } -bool TwitchIrcServer::hasSeparateWriteConnection() const -{ - return true; - // return getSettings()->twitchSeperateWriteConnection; -} - bool TwitchIrcServer::prepareToSend( const std::shared_ptr &channel) { @@ -779,4 +924,188 @@ void TwitchIrcServer::dropSeventvChannel(const QString &userID, } } +void TwitchIrcServer::markChannelsConnected() +{ + this->forEachChannel([](const ChannelPtr &chan) { + if (auto *channel = dynamic_cast(chan.get())) + { + channel->markConnected(); + } + }); +} + +void TwitchIrcServer::addFakeMessage(const QString &data) +{ + auto *fakeMessage = Communi::IrcMessage::fromData( + data.toUtf8(), this->readConnection_.get()); + + if (fakeMessage->command() == "PRIVMSG") + { + this->privateMessageReceived( + static_cast(fakeMessage)); + } + else + { + this->readConnectionMessageReceived(fakeMessage); + } +} + +void TwitchIrcServer::addGlobalSystemMessage(const QString &messageText) +{ + std::lock_guard lock(this->channelMutex); + + MessageBuilder b(systemMessage, messageText); + auto message = b.release(); + + for (std::weak_ptr &weak : this->channels.values()) + { + std::shared_ptr chan = weak.lock(); + if (!chan) + { + continue; + } + + chan->addMessage(message, MessageContext::Original); + } +} + +void TwitchIrcServer::forEachChannel(std::function func) +{ + std::lock_guard lock(this->channelMutex); + + for (std::weak_ptr &weak : this->channels.values()) + { + ChannelPtr chan = weak.lock(); + if (!chan) + { + continue; + } + + func(chan); + } +} + +void TwitchIrcServer::connect() +{ + this->disconnect(); + + this->initializeConnection(this->writeConnection_.get(), + ConnectionType::Write); + this->initializeConnection(this->readConnection_.get(), + ConnectionType::Read); +} + +void TwitchIrcServer::disconnect() +{ + std::lock_guard locker(this->connectionMutex_); + + this->readConnection_->close(); + this->writeConnection_->close(); +} + +void TwitchIrcServer::sendMessage(const QString &channelName, + const QString &message) +{ + this->sendRawMessage("PRIVMSG #" + channelName + " :" + message); +} + +void TwitchIrcServer::sendRawMessage(const QString &rawMessage) +{ + std::lock_guard locker(this->connectionMutex_); + + this->writeConnection_->sendRaw(rawMessage); +} + +ChannelPtr TwitchIrcServer::getOrAddChannel(const QString &dirtyChannelName) +{ + auto channelName = this->cleanChannelName(dirtyChannelName); + + // try get channel + ChannelPtr chan = this->getChannelOrEmpty(channelName); + if (chan != Channel::getEmpty()) + { + return chan; + } + + std::lock_guard lock(this->channelMutex); + + // value doesn't exist + chan = this->createChannel(channelName); + if (!chan) + { + return Channel::getEmpty(); + } + + this->channels.insert(channelName, chan); + this->connections_.managedConnect(chan->destroyed, [this, channelName] { + // fourtf: issues when the server itself is destroyed + + qCDebug(chatterinoIrc) << "[TwitchIrcServer::addChannel]" << channelName + << "was destroyed"; + this->channels.remove(channelName); + + if (this->readConnection_) + { + this->readConnection_->sendRaw("PART #" + channelName); + } + }); + + // join IRC channel + { + std::lock_guard lock2(this->connectionMutex_); + + if (this->readConnection_) + { + if (this->readConnection_->isConnected()) + { + this->joinBucket_->send(channelName); + } + } + } + + return chan; +} + +ChannelPtr TwitchIrcServer::getChannelOrEmpty(const QString &dirtyChannelName) +{ + auto channelName = this->cleanChannelName(dirtyChannelName); + + std::lock_guard lock(this->channelMutex); + + // try get special channel + ChannelPtr chan = this->getCustomChannel(channelName); + if (chan) + { + return chan; + } + + // value exists + auto it = this->channels.find(channelName); + if (it != this->channels.end()) + { + chan = it.value().lock(); + + if (chan) + { + return chan; + } + } + + return Channel::getEmpty(); +} + +void TwitchIrcServer::open(ConnectionType type) +{ + std::lock_guard lock(this->connectionMutex_); + + if (type == ConnectionType::Write) + { + this->writeConnection_->open(); + } + if (type == ConnectionType::Read) + { + this->readConnection_->open(); + } +} + } // namespace chatterino diff --git a/src/providers/twitch/TwitchIrcServer.hpp b/src/providers/twitch/TwitchIrcServer.hpp index 85c0a5677d5..e47f9f4ee68 100644 --- a/src/providers/twitch/TwitchIrcServer.hpp +++ b/src/providers/twitch/TwitchIrcServer.hpp @@ -2,12 +2,18 @@ #include "common/Atomic.hpp" #include "common/Channel.hpp" -#include "providers/irc/AbstractIrcServer.hpp" +#include "common/Common.hpp" +#include "providers/irc/IrcConnection2.hpp" +#include "util/RatelimitBucket.hpp" +#include +#include #include #include +#include #include +#include #include namespace chatterino { @@ -18,11 +24,30 @@ class TwitchChannel; class BttvEmotes; class FfzEmotes; class SeventvEmotes; +class RatelimitBucket; class ITwitchIrcServer { public: + ITwitchIrcServer() = default; virtual ~ITwitchIrcServer() = default; + ITwitchIrcServer(const ITwitchIrcServer &) = delete; + ITwitchIrcServer(ITwitchIrcServer &&) = delete; + ITwitchIrcServer &operator=(const ITwitchIrcServer &) = delete; + ITwitchIrcServer &operator=(ITwitchIrcServer &&) = delete; + + virtual void connect() = 0; + + virtual void sendRawMessage(const QString &rawMessage) = 0; + + virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0; + virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0; + + virtual void addFakeMessage(const QString &data) = 0; + + virtual void addGlobalSystemMessage(const QString &messageText) = 0; + + virtual void forEachChannel(std::function func) = 0; virtual void forEachChannelAndSpecialChannels( std::function func) = 0; @@ -46,9 +71,14 @@ class ITwitchIrcServer // Update this interface with TwitchIrcServer methods as needed }; -class TwitchIrcServer final : public AbstractIrcServer, public ITwitchIrcServer +class TwitchIrcServer final : public ITwitchIrcServer, public QObject { public: + enum class ConnectionType { + Read, + Write, + }; + TwitchIrcServer(); ~TwitchIrcServer() override = default; @@ -88,6 +118,25 @@ class TwitchIrcServer final : public AbstractIrcServer, public ITwitchIrcServer void dropSeventvChannel(const QString &userID, const QString &emoteSetID) override; + void addFakeMessage(const QString &data) override; + + void addGlobalSystemMessage(const QString &messageText) override; + + // iteration + void forEachChannel(std::function func) override; + + void connect() override; + void disconnect(); + + void sendMessage(const QString &channelName, const QString &message); + void sendRawMessage(const QString &rawMessage) override; + + ChannelPtr getOrAddChannel(const QString &dirtyChannelName) override; + + ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) override; + + void open(ConnectionType type); + private: Atomic lastUserThatWhisperedMe; @@ -109,19 +158,21 @@ class TwitchIrcServer final : public AbstractIrcServer, public ITwitchIrcServer void setLastUserThatWhisperedMe(const QString &user) override; protected: - void initializeConnection(IrcConnection *connection, - ConnectionType type) override; - std::shared_ptr createChannel(const QString &channelName) override; + void initializeConnection(IrcConnection *connection, ConnectionType type); + std::shared_ptr createChannel(const QString &channelName); + + void privateMessageReceived(Communi::IrcPrivateMessage *message); + void readConnectionMessageReceived(Communi::IrcMessage *message); + void writeConnectionMessageReceived(Communi::IrcMessage *message); - void privateMessageReceived(Communi::IrcPrivateMessage *message) override; - void readConnectionMessageReceived(Communi::IrcMessage *message) override; - void writeConnectionMessageReceived(Communi::IrcMessage *message) override; + void onReadConnected(IrcConnection *connection); + void onWriteConnected(IrcConnection *connection); + void onDisconnected(); + void markChannelsConnected(); - std::shared_ptr getCustomChannel( - const QString &channelname) override; + std::shared_ptr getCustomChannel(const QString &channelname); - QString cleanChannelName(const QString &dirtyChannelName) override; - bool hasSeparateWriteConnection() const override; + QString cleanChannelName(const QString &dirtyChannelName); private: void onMessageSendRequested(const std::shared_ptr &channel, @@ -132,6 +183,23 @@ class TwitchIrcServer final : public AbstractIrcServer, public ITwitchIrcServer bool prepareToSend(const std::shared_ptr &channel); + QMap> channels; + std::mutex channelMutex; + + QObjectPtr writeConnection_ = nullptr; + QObjectPtr readConnection_ = nullptr; + + // Our rate limiting bucket for the Twitch join rate limits + // https://dev.twitch.tv/docs/irc/guide#rate-limits + QObjectPtr joinBucket_; + + QTimer reconnectTimer_; + int falloffCounter_ = 1; + + std::mutex connectionMutex_; + + pajlada::Signals::SignalHolder connections_; + std::mutex lastMessageMutex_; std::queue lastMessagePleb_; std::queue lastMessageMod_; From ee93234631bc5c189a6df2fd326d0432935ffdc9 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 12:56:10 +0200 Subject: [PATCH 2/6] Remove experimental IRC support --- src/Application.cpp | 6 - src/CMakeLists.txt | 17 - src/common/Channel.cpp | 4 - src/common/Channel.hpp | 3 - src/common/IrcColors.hpp | 62 --- .../commands/builtin/twitch/SendWhisper.cpp | 13 - src/messages/MessageBuilder.cpp | 127 ----- src/messages/MessageBuilder.hpp | 18 - src/providers/irc/AbstractIrcServer.cpp | 435 ------------------ src/providers/irc/AbstractIrcServer.hpp | 135 ------ src/providers/irc/Irc2.cpp | 289 ------------ src/providers/irc/Irc2.hpp | 71 --- src/providers/irc/IrcAccount.cpp | 36 -- src/providers/irc/IrcAccount.hpp | 26 -- src/providers/irc/IrcChannel2.cpp | 119 ----- src/providers/irc/IrcChannel2.hpp | 33 -- src/providers/irc/IrcCommands.cpp | 92 ---- src/providers/irc/IrcCommands.hpp | 14 - src/providers/irc/IrcMessageBuilder.cpp | 147 ------ src/providers/irc/IrcMessageBuilder.hpp | 56 --- src/providers/irc/IrcServer.cpp | 386 ---------------- src/providers/irc/IrcServer.hpp | 50 -- src/singletons/Settings.hpp | 8 - src/singletons/WindowManager.cpp | 21 - src/util/StreamLink.cpp | 1 - src/widgets/dialogs/IrcConnectionEditor.cpp | 98 ---- src/widgets/dialogs/IrcConnectionEditor.hpp | 38 -- src/widgets/dialogs/IrcConnectionEditor.ui | 261 ----------- src/widgets/dialogs/SelectChannelDialog.cpp | 190 +------- src/widgets/dialogs/SelectChannelDialog.hpp | 4 - src/widgets/dialogs/UserInfoPopup.cpp | 3 +- src/widgets/helper/ChannelView.cpp | 1 - src/widgets/settingspages/GeneralPage.cpp | 7 - src/widgets/splits/SplitContainer.cpp | 18 - 34 files changed, 2 insertions(+), 2787 deletions(-) delete mode 100644 src/common/IrcColors.hpp delete mode 100644 src/providers/irc/AbstractIrcServer.cpp delete mode 100644 src/providers/irc/AbstractIrcServer.hpp delete mode 100644 src/providers/irc/Irc2.cpp delete mode 100644 src/providers/irc/Irc2.hpp delete mode 100644 src/providers/irc/IrcAccount.cpp delete mode 100644 src/providers/irc/IrcAccount.hpp delete mode 100644 src/providers/irc/IrcChannel2.cpp delete mode 100644 src/providers/irc/IrcChannel2.hpp delete mode 100644 src/providers/irc/IrcCommands.cpp delete mode 100644 src/providers/irc/IrcCommands.hpp delete mode 100644 src/providers/irc/IrcMessageBuilder.cpp delete mode 100644 src/providers/irc/IrcMessageBuilder.hpp delete mode 100644 src/providers/irc/IrcServer.cpp delete mode 100644 src/providers/irc/IrcServer.hpp delete mode 100644 src/widgets/dialogs/IrcConnectionEditor.cpp delete mode 100644 src/widgets/dialogs/IrcConnectionEditor.hpp delete mode 100644 src/widgets/dialogs/IrcConnectionEditor.ui diff --git a/src/Application.cpp b/src/Application.cpp index b58c6d14761..4ae2862d692 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -32,7 +32,6 @@ #include "providers/bttv/BttvLiveUpdates.hpp" #include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/ffz/FfzBadges.hpp" -#include "providers/irc/Irc2.hpp" #include "providers/seventv/eventapi/Dispatch.hpp" #include "providers/seventv/eventapi/Subscription.hpp" #include "providers/seventv/SeventvBadges.hpp" @@ -223,11 +222,6 @@ void Application::initialize(Settings &settings, const Paths &paths) if (!this->args_.isFramelessEmbed) { getSettings()->currentVersion.setValue(CHATTERINO_VERSION); - - if (getSettings()->enableExperimentalIrc) - { - Irc::instance().load(); - } } this->accounts->load(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1998646b259..6c0056996d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -338,22 +338,8 @@ set(SOURCE_FILES providers/ffz/FfzUtil.cpp providers/ffz/FfzUtil.hpp - providers/irc/AbstractIrcServer.cpp - providers/irc/AbstractIrcServer.hpp - providers/irc/Irc2.cpp - providers/irc/Irc2.hpp - providers/irc/IrcAccount.cpp - providers/irc/IrcAccount.hpp - providers/irc/IrcChannel2.cpp - providers/irc/IrcChannel2.hpp - providers/irc/IrcCommands.cpp - providers/irc/IrcCommands.hpp providers/irc/IrcConnection2.cpp providers/irc/IrcConnection2.hpp - providers/irc/IrcMessageBuilder.cpp - providers/irc/IrcMessageBuilder.hpp - providers/irc/IrcServer.cpp - providers/irc/IrcServer.hpp providers/links/LinkInfo.cpp providers/links/LinkInfo.hpp @@ -575,9 +561,6 @@ set(SOURCE_FILES widgets/dialogs/EditHotkeyDialog.hpp widgets/dialogs/EmotePopup.cpp widgets/dialogs/EmotePopup.hpp - widgets/dialogs/IrcConnectionEditor.cpp - widgets/dialogs/IrcConnectionEditor.hpp - widgets/dialogs/IrcConnectionEditor.ui widgets/dialogs/LastRunCrashDialog.cpp widgets/dialogs/LastRunCrashDialog.hpp widgets/dialogs/LoginDialog.cpp diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index 4e5d19e7dc7..b3372615187 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -3,8 +3,6 @@ #include "Application.hpp" #include "messages/Message.hpp" #include "messages/MessageBuilder.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" #include "providers/twitch/IrcMessageHandler.hpp" #include "singletons/Emotes.hpp" #include "singletons/Logging.hpp" @@ -36,8 +34,6 @@ Channel::Channel(const QString &name, Type type) { this->platform_ = "twitch"; } - - // Irc platform is set through IrcChannel2 ctor } Channel::~Channel() diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp index c2da9e67b75..880249e622b 100644 --- a/src/common/Channel.hpp +++ b/src/common/Channel.hpp @@ -53,7 +53,6 @@ class Channel : public std::enable_shared_from_this TwitchLive, TwitchAutomod, TwitchEnd, - Irc, Misc, }; @@ -185,8 +184,6 @@ constexpr magic_enum::customize::customize_t return "live"; case Type::TwitchAutomod: return "automod"; - case Type::Irc: - return "irc"; case Type::Misc: return "misc"; default: diff --git a/src/common/IrcColors.hpp b/src/common/IrcColors.hpp deleted file mode 100644 index 9721105650b..00000000000 --- a/src/common/IrcColors.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include - -namespace chatterino { - -// Colors taken from https://modern.ircdocs.horse/formatting.html -static QMap IRC_COLORS = { - {0, QColor("white")}, {1, QColor("black")}, - {2, QColor("blue")}, {3, QColor("green")}, - {4, QColor("red")}, {5, QColor("brown")}, - {6, QColor("purple")}, {7, QColor("orange")}, - {8, QColor("yellow")}, {9, QColor("lightgreen")}, - {10, QColor("cyan")}, {11, QColor("lightcyan")}, - {12, QColor("lightblue")}, {13, QColor("pink")}, - {14, QColor("gray")}, {15, QColor("lightgray")}, - {16, QColor("#470000")}, {17, QColor("#472100")}, - {18, QColor("#474700")}, {19, QColor("#324700")}, - {20, QColor("#004700")}, {21, QColor("#00472c")}, - {22, QColor("#004747")}, {23, QColor("#002747")}, - {24, QColor("#000047")}, {25, QColor("#2e0047")}, - {26, QColor("#470047")}, {27, QColor("#47002a")}, - {28, QColor("#740000")}, {29, QColor("#743a00")}, - {30, QColor("#747400")}, {31, QColor("#517400")}, - {32, QColor("#007400")}, {33, QColor("#007449")}, - {34, QColor("#007474")}, {35, QColor("#004074")}, - {36, QColor("#000074")}, {37, QColor("#4b0074")}, - {38, QColor("#740074")}, {39, QColor("#740045")}, - {40, QColor("#b50000")}, {41, QColor("#b56300")}, - {42, QColor("#b5b500")}, {43, QColor("#7db500")}, - {44, QColor("#00b500")}, {45, QColor("#00b571")}, - {46, QColor("#00b5b5")}, {47, QColor("#0063b5")}, - {48, QColor("#0000b5")}, {49, QColor("#7500b5")}, - {50, QColor("#b500b5")}, {51, QColor("#b5006b")}, - {52, QColor("#ff0000")}, {53, QColor("#ff8c00")}, - {54, QColor("#ffff00")}, {55, QColor("#b2ff00")}, - {56, QColor("#00ff00")}, {57, QColor("#00ffa0")}, - {58, QColor("#00ffff")}, {59, QColor("#008cff")}, - {60, QColor("#0000ff")}, {61, QColor("#a500ff")}, - {62, QColor("#ff00ff")}, {63, QColor("#ff0098")}, - {64, QColor("#ff5959")}, {65, QColor("#ffb459")}, - {66, QColor("#ffff71")}, {67, QColor("#cfff60")}, - {68, QColor("#6fff6f")}, {69, QColor("#65ffc9")}, - {70, QColor("#6dffff")}, {71, QColor("#59b4ff")}, - {72, QColor("#5959ff")}, {73, QColor("#c459ff")}, - {74, QColor("#ff66ff")}, {75, QColor("#ff59bc")}, - {76, QColor("#ff9c9c")}, {77, QColor("#ffd39c")}, - {78, QColor("#ffff9c")}, {79, QColor("#e2ff9c")}, - {80, QColor("#9cff9c")}, {81, QColor("#9cffdb")}, - {82, QColor("#9cffff")}, {83, QColor("#9cd3ff")}, - {84, QColor("#9c9cff")}, {85, QColor("#dc9cff")}, - {86, QColor("#ff9cff")}, {87, QColor("#ff94d3")}, - {88, QColor("#000000")}, {89, QColor("#131313")}, - {90, QColor("#282828")}, {91, QColor("#363636")}, - {92, QColor("#4d4d4d")}, {93, QColor("#656565")}, - {94, QColor("#818181")}, {95, QColor("#9f9f9f")}, - {96, QColor("#bcbcbc")}, {97, QColor("#e2e2e2")}, - {98, QColor("#ffffff")}, -}; - -} // namespace chatterino diff --git a/src/controllers/commands/builtin/twitch/SendWhisper.cpp b/src/controllers/commands/builtin/twitch/SendWhisper.cpp index 3a33ee973e1..cb965d5793d 100644 --- a/src/controllers/commands/builtin/twitch/SendWhisper.cpp +++ b/src/controllers/commands/builtin/twitch/SendWhisper.cpp @@ -9,8 +9,6 @@ #include "messages/MessageElement.hpp" #include "providers/bttv/BttvEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" #include "providers/twitch/api/Helix.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "providers/twitch/TwitchIrcServer.hpp" @@ -242,17 +240,6 @@ QString sendWhisper(const CommandContext &ctx) return ""; } - // we must be on IRC - auto *ircChannel = dynamic_cast(ctx.channel.get()); - if (ircChannel == nullptr) - { - // give up - return ""; - } - - auto *server = ircChannel->server(); - server->sendWhisper(target, message); - return ""; } diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp index ae78ab71120..29f4abb7fbb 100644 --- a/src/messages/MessageBuilder.cpp +++ b/src/messages/MessageBuilder.cpp @@ -1,10 +1,8 @@ #include "messages/MessageBuilder.hpp" #include "Application.hpp" -#include "common/IrcColors.hpp" #include "common/LinkParser.hpp" #include "controllers/accounts/AccountController.hpp" -#include "messages/Image.hpp" #include "messages/Message.hpp" #include "messages/MessageColor.hpp" #include "messages/MessageElement.hpp" @@ -12,18 +10,12 @@ #include "providers/twitch/PubSubActions.hpp" #include "providers/twitch/TwitchAccount.hpp" #include "singletons/Emotes.hpp" -#include "singletons/Resources.hpp" -#include "singletons/Theme.hpp" #include "util/FormatTime.hpp" #include namespace { -QRegularExpression IRC_COLOR_PARSE_REGEX( - "(\u0003(\\d{1,2})?(,(\\d{1,2}))?|\u000f)", - QRegularExpression::UseUnicodePropertiesOption); - QString formatUpdatedEmoteList(const QString &platform, const std::vector &emoteNames, bool isAdd, bool isFirstWord) @@ -679,107 +671,6 @@ void MessageBuilder::addLink(const linkparser::Parsed &parsedLink, getApp()->getLinkResolver()->resolve(el->linkInfo()); } -void MessageBuilder::addIrcMessageText(const QString &text) -{ - this->message().messageText = text; - - auto words = text.split(' '); - MessageColor defaultColorType = MessageColor::Text; - const auto &defaultColor = - defaultColorType.getColor(*getApp()->getThemes()); - QColor textColor = defaultColor; - int fg = -1; - int bg = -1; - - for (const auto &string : words) - { - if (string.isEmpty()) - { - continue; - } - - // Actually just text - auto link = linkparser::parse(string); - if (link) - { - this->addLink(*link, string); - continue; - } - - // Does the word contain a color changer? If so, split on it. - // Add color indicators, then combine into the same word with the color being changed - - auto i = IRC_COLOR_PARSE_REGEX.globalMatch(string); - - if (!i.hasNext()) - { - this->addIrcWord(string, textColor); - continue; - } - - int lastPos = 0; - - while (i.hasNext()) - { - auto match = i.next(); - - if (lastPos != match.capturedStart() && match.capturedStart() != 0) - { - if (fg >= 0 && fg <= 98) - { - textColor = IRC_COLORS[fg]; - getApp()->getThemes()->normalizeColor(textColor); - } - else - { - textColor = defaultColor; - } - this->addIrcWord( - string.mid(lastPos, match.capturedStart() - lastPos), - textColor, false); - lastPos = match.capturedStart() + match.capturedLength(); - } - if (!match.captured(1).isEmpty()) - { - fg = -1; - bg = -1; - } - - if (!match.captured(2).isEmpty()) - { - fg = match.captured(2).toInt(nullptr); - } - else - { - fg = -1; - } - if (!match.captured(4).isEmpty()) - { - bg = match.captured(4).toInt(nullptr); - } - else if (fg == -1) - { - bg = -1; - } - - lastPos = match.capturedStart() + match.capturedLength(); - } - - if (fg >= 0 && fg <= 98) - { - textColor = IRC_COLORS[fg]; - getApp()->getThemes()->normalizeColor(textColor); - } - else - { - textColor = defaultColor; - } - this->addIrcWord(string.mid(lastPos), textColor); - } - - this->message().elements.back()->setTrailingSpace(false); -} - void MessageBuilder::addTextOrEmoji(EmotePtr emote) { this->emplace(emote, MessageElementFlag::EmojiAll); @@ -806,24 +697,6 @@ void MessageBuilder::addTextOrEmoji(const QString &string) } } -void MessageBuilder::addIrcWord(const QString &text, const QColor &color, - bool addSpace) -{ - this->textColor_ = color; - for (auto &variant : getApp()->getEmotes()->getEmojis()->parse(text)) - { - boost::apply_visitor( - [&](auto &&arg) { - this->addTextOrEmoji(arg); - }, - variant); - if (!addSpace) - { - this->message().elements.back()->setTrailingSpace(false); - } - } -} - TextElement *MessageBuilder::emplaceSystemTextAndUpdate(const QString &text, QString &toUpdate) { diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp index 89de9921260..e9844467d07 100644 --- a/src/messages/MessageBuilder.hpp +++ b/src/messages/MessageBuilder.hpp @@ -116,13 +116,6 @@ class MessageBuilder void append(std::unique_ptr element); void addLink(const linkparser::Parsed &parsedLink, const QString &source); - /** - * Adds the text, applies irc colors, adds links, - * and updates the message's messageText. - * See https://modern.ircdocs.horse/formatting.html - */ - void addIrcMessageText(const QString &text); - template // clang-format off // clang-format can be enabled once clang-format v11+ has been installed in CI @@ -155,17 +148,6 @@ class MessageBuilder TextElement *emplaceSystemTextAndUpdate(const QString &text, QString &toUpdate); - /** - * This will add the text and replace any emojis - * with an emoji emote-element. - * - * @param text Text to add - * @param color Color of the text - * @param addSpace true if a trailing space should be added after emojis - */ - void addIrcWord(const QString &text, const QColor &color, - bool addSpace = true); - std::shared_ptr message_; }; diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp deleted file mode 100644 index 599a8e0551c..00000000000 --- a/src/providers/irc/AbstractIrcServer.cpp +++ /dev/null @@ -1,435 +0,0 @@ -#include "providers/irc/AbstractIrcServer.hpp" - -#include "common/Channel.hpp" -#include "common/QLogging.hpp" -#include "messages/LimitedQueueSnapshot.hpp" -#include "messages/Message.hpp" -#include "messages/MessageBuilder.hpp" -#include "providers/twitch/TwitchChannel.hpp" - -#include - -namespace chatterino { - -// Ratelimits for joinBucket_ -const int JOIN_RATELIMIT_BUDGET = 18; -const int JOIN_RATELIMIT_COOLDOWN = 12500; - -AbstractIrcServer::AbstractIrcServer() -{ - // Initialize the connections - // XXX: don't create write connection if there is no separate write connection. - this->writeConnection_.reset(new IrcConnection); - this->writeConnection_->moveToThread( - QCoreApplication::instance()->thread()); - - // Apply a leaky bucket rate limiting to JOIN messages - auto actuallyJoin = [&](QString message) { - if (!this->channels.contains(message)) - { - return; - } - this->readConnection_->sendRaw("JOIN #" + message); - }; - this->joinBucket_.reset(new RatelimitBucket( - JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this)); - - QObject::connect(this->writeConnection_.get(), - &Communi::IrcConnection::messageReceived, this, - [this](auto msg) { - this->writeConnectionMessageReceived(msg); - }); - QObject::connect(this->writeConnection_.get(), - &Communi::IrcConnection::connected, this, [this] { - this->onWriteConnected(this->writeConnection_.get()); - }); - this->connections_.managedConnect( - this->writeConnection_->connectionLost, [this](bool timeout) { - qCDebug(chatterinoIrc) - << "Write connection reconnect requested. Timeout:" << timeout; - this->writeConnection_->smartReconnect(); - }); - - // Listen to read connection message signals - this->readConnection_.reset(new IrcConnection); - this->readConnection_->moveToThread(QCoreApplication::instance()->thread()); - - QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::messageReceived, this, - [this](auto msg) { - this->readConnectionMessageReceived(msg); - }); - QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::privateMessageReceived, this, - [this](auto msg) { - this->privateMessageReceived(msg); - }); - QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::connected, this, [this] { - this->onReadConnected(this->readConnection_.get()); - }); - QObject::connect(this->readConnection_.get(), - &Communi::IrcConnection::disconnected, this, [this] { - this->onDisconnected(); - }); - this->connections_.managedConnect( - this->readConnection_->connectionLost, [this](bool timeout) { - qCDebug(chatterinoIrc) - << "Read connection reconnect requested. Timeout:" << timeout; - if (timeout) - { - // Show additional message since this is going to interrupt a - // connection that is still "connected" - this->addGlobalSystemMessage( - "Server connection timed out, reconnecting"); - } - this->readConnection_->smartReconnect(); - }); - this->connections_.managedConnect(this->readConnection_->heartbeat, [this] { - this->markChannelsConnected(); - }); -} - -void AbstractIrcServer::initializeIrc() -{ - assert(!this->initialized_); - - if (this->hasSeparateWriteConnection()) - { - this->initializeConnectionSignals(this->writeConnection_.get(), - ConnectionType::Write); - this->initializeConnectionSignals(this->readConnection_.get(), - ConnectionType::Read); - } - else - { - this->initializeConnectionSignals(this->readConnection_.get(), - ConnectionType::Both); - } - - this->initialized_ = true; -} - -void AbstractIrcServer::connect() -{ - assert(this->initialized_); - - this->disconnect(); - - if (this->hasSeparateWriteConnection()) - { - this->initializeConnection(this->writeConnection_.get(), Write); - this->initializeConnection(this->readConnection_.get(), Read); - } - else - { - this->initializeConnection(this->readConnection_.get(), Both); - } -} - -void AbstractIrcServer::open(ConnectionType type) -{ - std::lock_guard lock(this->connectionMutex_); - - if (type == Write) - { - this->writeConnection_->open(); - } - if (type & Read) - { - this->readConnection_->open(); - } -} - -void AbstractIrcServer::addGlobalSystemMessage(const QString &messageText) -{ - std::lock_guard lock(this->channelMutex); - - MessageBuilder b(systemMessage, messageText); - auto message = b.release(); - - for (std::weak_ptr &weak : this->channels.values()) - { - std::shared_ptr chan = weak.lock(); - if (!chan) - { - continue; - } - - chan->addMessage(message, MessageContext::Original); - } -} - -void AbstractIrcServer::disconnect() -{ - std::lock_guard locker(this->connectionMutex_); - - this->readConnection_->close(); - if (this->hasSeparateWriteConnection()) - { - this->writeConnection_->close(); - } -} - -void AbstractIrcServer::sendMessage(const QString &channelName, - const QString &message) -{ - this->sendRawMessage("PRIVMSG #" + channelName + " :" + message); -} - -void AbstractIrcServer::sendRawMessage(const QString &rawMessage) -{ - std::lock_guard locker(this->connectionMutex_); - - if (this->hasSeparateWriteConnection()) - { - this->writeConnection_->sendRaw(rawMessage); - } - else - { - this->readConnection_->sendRaw(rawMessage); - } -} - -void AbstractIrcServer::writeConnectionMessageReceived( - Communi::IrcMessage *message) -{ - (void)message; -} - -ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName) -{ - auto channelName = this->cleanChannelName(dirtyChannelName); - - // try get channel - ChannelPtr chan = this->getChannelOrEmpty(channelName); - if (chan != Channel::getEmpty()) - { - return chan; - } - - std::lock_guard lock(this->channelMutex); - - // value doesn't exist - chan = this->createChannel(channelName); - if (!chan) - { - return Channel::getEmpty(); - } - - this->channels.insert(channelName, chan); - this->connections_.managedConnect(chan->destroyed, [this, channelName] { - // fourtf: issues when the server itself is destroyed - - qCDebug(chatterinoIrc) << "[AbstractIrcServer::addChannel]" - << channelName << "was destroyed"; - this->channels.remove(channelName); - - if (this->readConnection_) - { - this->readConnection_->sendRaw("PART #" + channelName); - } - }); - - // join IRC channel - { - std::lock_guard lock2(this->connectionMutex_); - - if (this->readConnection_) - { - if (this->readConnection_->isConnected()) - { - this->joinBucket_->send(channelName); - } - } - } - - return chan; -} - -ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName) -{ - auto channelName = this->cleanChannelName(dirtyChannelName); - - std::lock_guard lock(this->channelMutex); - - // try get special channel - ChannelPtr chan = this->getCustomChannel(channelName); - if (chan) - { - return chan; - } - - // value exists - auto it = this->channels.find(channelName); - if (it != this->channels.end()) - { - chan = it.value().lock(); - - if (chan) - { - return chan; - } - } - - return Channel::getEmpty(); -} - -std::vector> AbstractIrcServer::getChannels() -{ - std::lock_guard lock(this->channelMutex); - std::vector> channels; - - for (auto &&weak : this->channels.values()) - { - channels.push_back(weak); - } - - return channels; -} - -void AbstractIrcServer::onReadConnected(IrcConnection *connection) -{ - (void)connection; - - std::lock_guard lock(this->channelMutex); - - // join channels - for (auto &&weak : this->channels) - { - if (auto channel = weak.lock()) - { - this->joinBucket_->send(channel->getName()); - } - } - - // connected/disconnected message - auto connectedMsg = makeSystemMessage("connected"); - connectedMsg->flags.set(MessageFlag::ConnectedMessage); - auto reconnected = makeSystemMessage("reconnected"); - reconnected->flags.set(MessageFlag::ConnectedMessage); - - for (std::weak_ptr &weak : this->channels.values()) - { - std::shared_ptr chan = weak.lock(); - if (!chan) - { - continue; - } - - LimitedQueueSnapshot snapshot = chan->getMessageSnapshot(); - - bool replaceMessage = - snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has( - MessageFlag::DisconnectedMessage); - - if (replaceMessage) - { - chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected); - } - else - { - chan->addMessage(connectedMsg, MessageContext::Original); - } - } - - this->falloffCounter_ = 1; -} - -void AbstractIrcServer::onWriteConnected(IrcConnection *connection) -{ - (void)connection; -} - -void AbstractIrcServer::onDisconnected() -{ - std::lock_guard lock(this->channelMutex); - - MessageBuilder b(systemMessage, "disconnected"); - b->flags.set(MessageFlag::DisconnectedMessage); - auto disconnectedMsg = b.release(); - - for (std::weak_ptr &weak : this->channels.values()) - { - std::shared_ptr chan = weak.lock(); - if (!chan) - { - continue; - } - - chan->addMessage(disconnectedMsg, MessageContext::Original); - - if (auto *channel = dynamic_cast(chan.get())) - { - channel->markDisconnected(); - } - } -} - -void AbstractIrcServer::markChannelsConnected() -{ - this->forEachChannel([](const ChannelPtr &chan) { - if (auto *channel = dynamic_cast(chan.get())) - { - channel->markConnected(); - } - }); -} - -std::shared_ptr AbstractIrcServer::getCustomChannel( - const QString &channelName) -{ - (void)channelName; - return nullptr; -} - -QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName) -{ - // This function is a Noop only for IRC, for Twitch it removes a leading '#' and lowercases the name - return dirtyChannelName; -} - -void AbstractIrcServer::addFakeMessage(const QString &data) -{ - auto *fakeMessage = Communi::IrcMessage::fromData( - data.toUtf8(), this->readConnection_.get()); - - if (fakeMessage->command() == "PRIVMSG") - { - this->privateMessageReceived( - static_cast(fakeMessage)); - } - else - { - this->readConnectionMessageReceived(fakeMessage); - } -} - -void AbstractIrcServer::privateMessageReceived( - Communi::IrcPrivateMessage *message) -{ - (void)message; -} - -void AbstractIrcServer::readConnectionMessageReceived( - Communi::IrcMessage *message) -{ -} - -void AbstractIrcServer::forEachChannel(std::function func) -{ - std::lock_guard lock(this->channelMutex); - - for (std::weak_ptr &weak : this->channels.values()) - { - ChannelPtr chan = weak.lock(); - if (!chan) - { - continue; - } - - func(chan); - } -} - -} // namespace chatterino diff --git a/src/providers/irc/AbstractIrcServer.hpp b/src/providers/irc/AbstractIrcServer.hpp deleted file mode 100644 index 0b626f9e0be..00000000000 --- a/src/providers/irc/AbstractIrcServer.hpp +++ /dev/null @@ -1,135 +0,0 @@ -#pragma once - -#include "common/Common.hpp" -#include "providers/irc/IrcConnection2.hpp" -#include "util/RatelimitBucket.hpp" - -#include -#include -#include - -#include -#include - -namespace chatterino { - -class Channel; -using ChannelPtr = std::shared_ptr; -class RatelimitBucket; - -class IAbstractIrcServer -{ -public: - virtual void connect() = 0; - - virtual void sendRawMessage(const QString &rawMessage) = 0; - - virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0; - virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0; - - virtual void addFakeMessage(const QString &data) = 0; - - virtual void addGlobalSystemMessage(const QString &messageText) = 0; - - virtual void forEachChannel(std::function func) = 0; -}; - -class AbstractIrcServer : public IAbstractIrcServer, public QObject -{ -public: - enum ConnectionType { Read = 1, Write = 2, Both = 3 }; - - ~AbstractIrcServer() override = default; - AbstractIrcServer(const AbstractIrcServer &) = delete; - AbstractIrcServer(AbstractIrcServer &&) = delete; - AbstractIrcServer &operator=(const AbstractIrcServer &) = delete; - AbstractIrcServer &operator=(AbstractIrcServer &&) = delete; - - // initializeIrc must be called from the derived class - // this allows us to initialize the abstract IRC server based on the derived class's parameters - void initializeIrc(); - - // connection - void connect() final; - void disconnect(); - - void sendMessage(const QString &channelName, const QString &message); - void sendRawMessage(const QString &rawMessage) override; - - // channels - ChannelPtr getOrAddChannel(const QString &dirtyChannelName) final; - ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) final; - std::vector> getChannels(); - - // signals - pajlada::Signals::NoArgSignal connected; - pajlada::Signals::NoArgSignal disconnected; - - void addFakeMessage(const QString &data) final; - - void addGlobalSystemMessage(const QString &messageText) final; - - // iteration - void forEachChannel(std::function func) final; - -protected: - AbstractIrcServer(); - - // initializeConnectionSignals is called on a connection once in its lifetime. - // it can be used to connect signals to your class - virtual void initializeConnectionSignals(IrcConnection *connection, - ConnectionType type) - { - (void)connection; - (void)type; - } - - // initializeConnection is called every time before we try to connect to the IRC server - virtual void initializeConnection(IrcConnection *connection, - ConnectionType type) = 0; - - virtual std::shared_ptr createChannel( - const QString &channelName) = 0; - - virtual void privateMessageReceived(Communi::IrcPrivateMessage *message); - virtual void readConnectionMessageReceived(Communi::IrcMessage *message); - virtual void writeConnectionMessageReceived(Communi::IrcMessage *message); - - virtual void onReadConnected(IrcConnection *connection); - virtual void onWriteConnected(IrcConnection *connection); - virtual void onDisconnected(); - void markChannelsConnected(); - - virtual std::shared_ptr getCustomChannel( - const QString &channelName); - - virtual bool hasSeparateWriteConnection() const = 0; - virtual QString cleanChannelName(const QString &dirtyChannelName); - - void open(ConnectionType type); - - QMap> channels; - std::mutex channelMutex; - -private: - void initConnection(); - - QObjectPtr writeConnection_ = nullptr; - QObjectPtr readConnection_ = nullptr; - - // Our rate limiting bucket for the Twitch join rate limits - // https://dev.twitch.tv/docs/irc/guide#rate-limits - QObjectPtr joinBucket_; - - QTimer reconnectTimer_; - int falloffCounter_ = 1; - - std::mutex connectionMutex_; - - // bool autoReconnect_ = false; - pajlada::Signals::SignalHolder connections_; - - bool initialized_{false}; -}; - -} // namespace chatterino diff --git a/src/providers/irc/Irc2.cpp b/src/providers/irc/Irc2.cpp deleted file mode 100644 index 48b3cd0a130..00000000000 --- a/src/providers/irc/Irc2.cpp +++ /dev/null @@ -1,289 +0,0 @@ -#include "providers/irc/Irc2.hpp" - -#include "Application.hpp" -#include "common/Credentials.hpp" -#include "common/SignalVectorModel.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" -#include "singletons/Paths.hpp" -#include "util/CombinePath.hpp" -#include "util/RapidjsonHelpers.hpp" -#include "util/StandardItemHelper.hpp" - -#include -#include -#include - -#include - -namespace chatterino { - -namespace { - - QString configPath() - { - return combinePath(getApp()->getPaths().settingsDirectory, "irc.json"); - } - - class Model : public SignalVectorModel - { - public: - Model(QObject *parent) - : SignalVectorModel(6, parent) - { - } - - // turn a vector item into a model row - IrcServerData getItemFromRow(std::vector &row, - const IrcServerData &original) override - { - return IrcServerData{ - row[0]->data(Qt::EditRole).toString(), // host - row[1]->data(Qt::EditRole).toInt(), // port - row[2]->data(Qt::CheckStateRole).toBool(), // ssl - row[3]->data(Qt::EditRole).toString(), // user - row[4]->data(Qt::EditRole).toString(), // nick - row[5]->data(Qt::EditRole).toString(), // real - original.authType, // authType - original.connectCommands, // connectCommands - original.id, // id - }; - } - - // turns a row in the model into a vector item - void getRowFromItem(const IrcServerData &item, - std::vector &row) override - { - setStringItem(row[0], item.host, false); - setStringItem(row[1], QString::number(item.port)); - setBoolItem(row[2], item.ssl); - setStringItem(row[3], item.user); - setStringItem(row[4], item.nick); - setStringItem(row[5], item.real); - } - }; - -} // namespace - -inline QString escape(QString str) -{ - return str.replace(":", "::"); -} - -// This returns a unique id for every server which is understandeable in the systems credential manager. -inline QString getCredentialName(const IrcServerData &data) -{ - return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" + - escape(data.host); -} - -void IrcServerData::getPassword( - QObject *receiver, std::function &&onLoaded) const -{ - Credentials::instance().get("irc", getCredentialName(*this), receiver, - std::move(onLoaded)); -} - -void IrcServerData::setPassword(const QString &password) -{ - Credentials::instance().set("irc", getCredentialName(*this), password); -} - -Irc::Irc() -{ - // We can safely ignore this signal connection since `connections` will always - // be destroyed before the Irc object - std::ignore = this->connections.itemInserted.connect([this](auto &&args) { - // make sure only one id can only exist for one server - assert(this->servers_.find(args.item.id) == this->servers_.end()); - - // add new server - if (auto ab = this->abandonedChannels_.find(args.item.id); - ab != this->abandonedChannels_.end()) - { - auto server = std::make_unique(args.item, ab->second); - - // set server of abandoned channels - for (auto weak : ab->second) - { - if (auto shared = weak.lock()) - { - if (auto *ircChannel = - dynamic_cast(shared.get())) - { - ircChannel->setServer(server.get()); - } - } - } - - // add new server with abandoned channels - this->servers_.emplace(args.item.id, std::move(server)); - this->abandonedChannels_.erase(ab); - } - else - { - // add new server - this->servers_.emplace(args.item.id, - std::make_unique(args.item)); - } - }); - - // We can safely ignore this signal connection since `connections` will always - // be destroyed before the Irc object - std::ignore = this->connections.itemRemoved.connect([this](auto &&args) { - // restore - if (auto server = this->servers_.find(args.item.id); - server != this->servers_.end()) - { - auto abandoned = server->second->getChannels(); - - // set server of abandoned servers to nullptr - for (auto weak : abandoned) - { - if (auto shared = weak.lock()) - { - if (auto *ircChannel = - dynamic_cast(shared.get())) - { - ircChannel->setServer(nullptr); - } - } - } - - this->abandonedChannels_[args.item.id] = abandoned; - this->servers_.erase(server); - } - - if (args.caller != Irc::noEraseCredentialCaller) - { - Credentials::instance().erase("irc", getCredentialName(args.item)); - } - }); - - // We can safely ignore this signal connection since `connections` will always - // be destroyed before the Irc object - std::ignore = this->connections.delayedItemsChanged.connect([this] { - this->save(); - }); -} - -QAbstractTableModel *Irc::newConnectionModel(QObject *parent) -{ - auto *model = new Model(parent); - model->initialize(&this->connections); - return model; -} - -ChannelPtr Irc::getOrAddChannel(int id, QString name) -{ - if (auto server = this->servers_.find(id); server != this->servers_.end()) - { - return server->second->getOrAddChannel(name); - } - else - { - auto channel = std::make_shared(name, nullptr); - - this->abandonedChannels_[id].push_back(channel); - - return std::move(channel); - } -} - -Irc &Irc::instance() -{ - static Irc irc; - return irc; -} - -int Irc::uniqueId() -{ - int i = this->currentId_ + 1; - auto it = this->servers_.find(i); - auto it2 = this->abandonedChannels_.find(i); - - while (it != this->servers_.end() || it2 != this->abandonedChannels_.end()) - { - i++; - it = this->servers_.find(i); - it2 = this->abandonedChannels_.find(i); - } - - return (this->currentId_ = i); -} - -void Irc::save() -{ - QJsonDocument doc; - QJsonObject root; - QJsonArray servers; - - for (auto &&conn : this->connections) - { - QJsonObject obj; - obj.insert("host", conn.host); - obj.insert("port", conn.port); - obj.insert("ssl", conn.ssl); - obj.insert("username", conn.user); - obj.insert("nickname", conn.nick); - obj.insert("realname", conn.real); - obj.insert("connectCommands", - QJsonArray::fromStringList(conn.connectCommands)); - obj.insert("id", conn.id); - obj.insert("authType", int(conn.authType)); - - servers.append(obj); - } - - root.insert("servers", servers); - doc.setObject(root); - - QSaveFile file(configPath()); - file.open(QIODevice::WriteOnly); - file.write(doc.toJson()); - file.commit(); -} - -void Irc::load() -{ - if (this->loaded_) - { - return; - } - this->loaded_ = true; - - QString config = configPath(); - QFile file(configPath()); - file.open(QIODevice::ReadOnly); - auto object = QJsonDocument::fromJson(file.readAll()).object(); - - std::unordered_set ids; - - // load servers - for (auto server : object.value("servers").toArray()) - { - auto obj = server.toObject(); - IrcServerData data; - data.host = obj.value("host").toString(data.host); - data.port = obj.value("port").toInt(data.port); - data.ssl = obj.value("ssl").toBool(data.ssl); - data.user = obj.value("username").toString(data.user); - data.nick = obj.value("nickname").toString(data.nick); - data.real = obj.value("realname").toString(data.real); - data.connectCommands = - obj.value("connectCommands").toVariant().toStringList(); - data.id = obj.value("id").toInt(data.id); - data.authType = - IrcAuthType(obj.value("authType").toInt(int(data.authType))); - - // duplicate id's are not allowed :( - if (ids.find(data.id) == ids.end()) - { - ids.insert(data.id); - - this->connections.append(data); - } - } -} - -} // namespace chatterino diff --git a/src/providers/irc/Irc2.hpp b/src/providers/irc/Irc2.hpp deleted file mode 100644 index 915fbc6b953..00000000000 --- a/src/providers/irc/Irc2.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "common/SignalVector.hpp" - -#include - -#include - -class QAbstractTableModel; - -namespace chatterino { - -class Channel; -using ChannelPtr = std::shared_ptr; -class IrcServer; - -enum class IrcAuthType { Anonymous, Custom, Pass, Sasl }; - -struct IrcServerData { - QString host; - int port = 6697; - bool ssl = true; - - QString user; - QString nick; - QString real; - - IrcAuthType authType = IrcAuthType::Anonymous; - void getPassword(QObject *receiver, - std::function &&onLoaded) const; - void setPassword(const QString &password); - - QStringList connectCommands; - - int id; -}; - -class Irc -{ -public: - Irc(); - - static Irc &instance(); - - static inline void *const noEraseCredentialCaller = - reinterpret_cast(1); - - SignalVector connections; - QAbstractTableModel *newConnectionModel(QObject *parent); - - ChannelPtr getOrAddChannel(int serverId, QString name); - - void save(); - void load(); - - int uniqueId(); - -private: - int currentId_{}; - bool loaded_{}; - - // Servers have a unique id. - // When a server gets changed it gets removed and then added again. - // So we store the channels of that server in abandonedChannels_ temporarily. - // Or if the server got removed permanently then it's still stored there. - std::unordered_map> servers_; - std::unordered_map>> - abandonedChannels_; -}; - -} // namespace chatterino diff --git a/src/providers/irc/IrcAccount.cpp b/src/providers/irc/IrcAccount.cpp deleted file mode 100644 index 53030252154..00000000000 --- a/src/providers/irc/IrcAccount.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "providers/irc/IrcAccount.hpp" - -// namespace chatterino { -// -// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName, -// const QString -// &_realName, -// const QString &_password) -// : userName(_userName) -// , nickName(_nickName) -// , realName(_realName) -// , password(_password) -//{ -//} - -// const QString &IrcAccount::getUserName() const -//{ -// return this->userName; -//} - -// const QString &IrcAccount::getNickName() const -//{ -// return this->nickName; -//} - -// const QString &IrcAccount::getRealName() const -//{ -// return this->realName; -//} - -// const QString &IrcAccount::getPassword() const -//{ -// return this->password; -//} -// -//} // namespace chatterino diff --git a/src/providers/irc/IrcAccount.hpp b/src/providers/irc/IrcAccount.hpp deleted file mode 100644 index 2c4345b1040..00000000000 --- a/src/providers/irc/IrcAccount.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -// namespace chatterino { -// -// class IrcAccount -//{ -// public: -// IrcAccount(const QString &userName, const QString &nickName, const QString -// &realName, -// const QString &password); - -// const QString &getUserName() const; -// const QString &getNickName() const; -// const QString &getRealName() const; -// const QString &getPassword() const; - -// private: -// QString userName; -// QString nickName; -// QString realName; -// QString password; -//}; -// -//} // namespace chatterino diff --git a/src/providers/irc/IrcChannel2.cpp b/src/providers/irc/IrcChannel2.cpp deleted file mode 100644 index 76feb901b16..00000000000 --- a/src/providers/irc/IrcChannel2.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "providers/irc/IrcChannel2.hpp" - -#include "common/Channel.hpp" -#include "debug/AssertInGuiThread.hpp" -#include "messages/Message.hpp" -#include "messages/MessageBuilder.hpp" -#include "messages/MessageElement.hpp" -#include "providers/irc/IrcCommands.hpp" -#include "providers/irc/IrcMessageBuilder.hpp" -#include "providers/irc/IrcServer.hpp" -#include "util/Helpers.hpp" - -namespace chatterino { - -IrcChannel::IrcChannel(const QString &name, IrcServer *server) - : Channel(name, Channel::Type::Irc) - , ChannelChatters(*static_cast(this)) - , server_(server) -{ - auto *ircServer = this->server(); - if (ircServer != nullptr) - { - this->platform_ = - QString("irc-%1").arg(ircServer->userFriendlyIdentifier()); - } - else - { - this->platform_ = "irc-unknown"; - } -} - -void IrcChannel::sendMessage(const QString &message) -{ - assertInGuiThread(); - if (message.isEmpty()) - { - return; - } - - if (message.startsWith("/")) - { - auto index = message.indexOf(' ', 1); - QString command = message.mid(1, index - 1); - QString params = index == -1 ? "" : message.mid(index + 1); - - invokeIrcCommand(command, params, *this); - } - else - { - if (this->server() != nullptr) - { - this->server()->sendMessage(this->getName(), message); - if (this->server()->hasEcho()) - { - return; - } - MessageBuilder builder; - - builder - .emplace("#" + this->getName(), - MessageElementFlag::ChannelName, - MessageColor::System) - ->setLink({Link::JumpToChannel, this->getName()}); - - auto now = QDateTime::currentDateTime(); - builder.emplace(now.time()); - builder.message().serverReceivedTime = now; - - auto username = this->server()->nick(); - builder - .emplace( - username + ":", MessageElementFlag::Username, - getRandomColor(username), FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, username}); - builder.message().loginName = username; - builder.message().displayName = username; - - // message - builder.addIrcMessageText(message); - builder.message().messageText = message; - builder.message().searchText = username + ": " + message; - - this->addMessage(builder.release(), MessageContext::Original); - } - else - { - this->addSystemMessage("You are not connected."); - } - } -} - -IrcServer *IrcChannel::server() const -{ - assertInGuiThread(); - - return this->server_; -} - -void IrcChannel::setServer(IrcServer *server) -{ - assertInGuiThread(); - - this->server_ = server; -} - -bool IrcChannel::canReconnect() const -{ - return true; -} - -void IrcChannel::reconnect() -{ - if (this->server()) - { - this->server()->connect(); - } -} - -} // namespace chatterino diff --git a/src/providers/irc/IrcChannel2.hpp b/src/providers/irc/IrcChannel2.hpp deleted file mode 100644 index 75ff40ea473..00000000000 --- a/src/providers/irc/IrcChannel2.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "common/Channel.hpp" -#include "common/ChannelChatters.hpp" - -namespace chatterino { - -class Irc; -class IrcServer; - -class IrcChannel final : public Channel, public ChannelChatters -{ -public: - explicit IrcChannel(const QString &name, IrcServer *server); - - void sendMessage(const QString &message) override; - - // server may be nullptr - IrcServer *server() const; - - // Channel methods - bool canReconnect() const override; - void reconnect() override; - -private: - void setServer(IrcServer *server); - - IrcServer *server_; - - friend class Irc; -}; - -} // namespace chatterino diff --git a/src/providers/irc/IrcCommands.cpp b/src/providers/irc/IrcCommands.cpp deleted file mode 100644 index b1b2e8fc3c2..00000000000 --- a/src/providers/irc/IrcCommands.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "providers/irc/IrcCommands.hpp" - -#include "messages/MessageBuilder.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" -#include "util/QStringHash.hpp" - -namespace chatterino { - -Outcome invokeIrcCommand(const QString &commandName, const QString &allParams, - IrcChannel &channel) -{ - if (!channel.server()) - { - return Failure; - } - - // STATIC MESSAGES - static auto staticMessages = std::unordered_map{ - {"join", "/join is not supported. Press ctrl+r to change the " - "channel. If required use /raw JOIN #channel."}, - {"part", "/part is not supported. Press ctrl+r to change the " - "channel. If required use /raw PART #channel."}, - }; - auto cmd = commandName.toLower(); - - if (auto it = staticMessages.find(cmd); it != staticMessages.end()) - { - channel.addSystemMessage(it->second); - return Success; - } - - // CUSTOM COMMANDS - auto params = allParams.split(' '); - auto paramsAfter = [&](int i) { - return params.mid(i + 1).join(' '); - }; - - auto sendRaw = [&](QString str) { - channel.server()->sendRawMessage(str); - }; - - if (cmd == "msg") - { - channel.server()->sendWhisper(params[0], paramsAfter(0)); - } - else if (cmd == "away") - { - sendRaw("AWAY " + params[0] + " :" + paramsAfter(0)); - } - else if (cmd == "knock") - { - sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0)); - } - else if (cmd == "kick") - { - if (params.size() < 2) - { - channel.addSystemMessage( - "Usage: /kick [message]"); - return Failure; - } - const auto &channelParam = params[0]; - const auto &clientParam = params[1]; - const auto &messageParam = paramsAfter(1); - if (messageParam.isEmpty()) - { - sendRaw("KICK " + channelParam + " " + clientParam); - } - else - { - sendRaw("KICK " + channelParam + " " + clientParam + " :" + - messageParam); - } - } - else if (cmd == "wallops") - { - sendRaw("WALLOPS :" + allParams); - } - else if (cmd == "raw") - { - sendRaw(allParams); - } - else - { - sendRaw(cmd.toUpper() + " " + allParams); - } - - return Success; -} - -} // namespace chatterino diff --git a/src/providers/irc/IrcCommands.hpp b/src/providers/irc/IrcCommands.hpp deleted file mode 100644 index 38f7db173db..00000000000 --- a/src/providers/irc/IrcCommands.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "common/Outcome.hpp" - -#include - -namespace chatterino { - -class IrcChannel; - -Outcome invokeIrcCommand(const QString &command, const QString ¶ms, - IrcChannel &channel); - -} // namespace chatterino diff --git a/src/providers/irc/IrcMessageBuilder.cpp b/src/providers/irc/IrcMessageBuilder.cpp deleted file mode 100644 index 3b7fb30da25..00000000000 --- a/src/providers/irc/IrcMessageBuilder.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "providers/irc/IrcMessageBuilder.hpp" - -#include "controllers/ignores/IgnoreController.hpp" -#include "controllers/ignores/IgnorePhrase.hpp" -#include "messages/Message.hpp" -#include "messages/MessageColor.hpp" -#include "messages/MessageElement.hpp" -#include "singletons/Emotes.hpp" -#include "singletons/Settings.hpp" -#include "singletons/Theme.hpp" -#include "singletons/WindowManager.hpp" -#include "util/Helpers.hpp" -#include "util/IrcHelpers.hpp" -#include "widgets/Window.hpp" - -namespace chatterino { - -IrcMessageBuilder::IrcMessageBuilder( - Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage, - const MessageParseArgs &_args) - : SharedMessageBuilder(_channel, _ircMessage, _args) -{ -} - -IrcMessageBuilder::IrcMessageBuilder(Channel *_channel, - const Communi::IrcMessage *_ircMessage, - const MessageParseArgs &_args, - QString content, bool isAction) - : SharedMessageBuilder(_channel, _ircMessage, _args, content, isAction) -{ - assert(false); -} - -IrcMessageBuilder::IrcMessageBuilder( - const Communi::IrcNoticeMessage *_ircMessage, const MessageParseArgs &_args) - : SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args, - _ircMessage->content(), false) -{ -} - -IrcMessageBuilder::IrcMessageBuilder( - const Communi::IrcPrivateMessage *_ircMessage, - const MessageParseArgs &_args) - : SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args, - _ircMessage->content(), false) - , whisperTarget_(_ircMessage->target()) -{ -} - -MessagePtr IrcMessageBuilder::build() -{ - // PARSE - this->parse(); - this->usernameColor_ = getRandomColor(this->ircMessage->nick()); - - // PUSH ELEMENTS - this->appendChannelName(); - - this->message().serverReceivedTime = calculateMessageTime(this->ircMessage); - this->emplace(this->message().serverReceivedTime.time()); - - this->appendUsername(); - - // message - this->addIrcMessageText(this->originalMessage_); - - QString stylizedUsername = - this->stylizeUsername(this->userName, this->message()); - - this->message().searchText = stylizedUsername + " " + - this->message().localizedName + " " + - this->userName + ": " + this->originalMessage_; - - // highlights - this->parseHighlights(); - - // highlighting incoming whispers if requested per setting - if (this->args.isReceivedWhisper && getSettings()->highlightInlineWhispers) - { - this->message().flags.set(MessageFlag::HighlightedWhisper, true); - } - - return this->release(); -} - -void IrcMessageBuilder::appendUsername() -{ - QString username = this->userName; - this->message().loginName = username; - this->message().displayName = username; - - // The full string that will be rendered in the chat widget - QString usernameText = - SharedMessageBuilder::stylizeUsername(username, this->message()); - - if (this->args.isReceivedWhisper) - { - this->emplace(usernameText, MessageElementFlag::Username, - this->usernameColor_, - FontStyle::ChatMediumBold) - ->setLink({Link::UserWhisper, this->message().displayName}); - - // Separator - this->emplace("->", MessageElementFlag::Username, - MessageColor::System, FontStyle::ChatMedium); - - if (this->whisperTarget_.isEmpty()) - { - this->emplace("you:", MessageElementFlag::Username); - } - else - { - this->emplace(this->whisperTarget_ + ":", - MessageElementFlag::Username, - getRandomColor(this->whisperTarget_), - FontStyle::ChatMediumBold); - } - } - else if (this->args.isSentWhisper) - { - this->emplace(usernameText, MessageElementFlag::Username, - this->usernameColor_, - FontStyle::ChatMediumBold); - - // Separator - this->emplace("->", MessageElementFlag::Username, - MessageColor::System, FontStyle::ChatMedium); - - this->emplace( - this->whisperTarget_ + ":", MessageElementFlag::Username, - getRandomColor(this->whisperTarget_), FontStyle::ChatMediumBold) - ->setLink({Link::UserWhisper, this->whisperTarget_}); - } - else - { - if (!this->action_) - { - usernameText += ":"; - } - this->emplace(usernameText, MessageElementFlag::Username, - this->usernameColor_, - FontStyle::ChatMediumBold) - ->setLink({Link::UserInfo, this->message().loginName}); - } -} - -} // namespace chatterino diff --git a/src/providers/irc/IrcMessageBuilder.hpp b/src/providers/irc/IrcMessageBuilder.hpp deleted file mode 100644 index 820a893809f..00000000000 --- a/src/providers/irc/IrcMessageBuilder.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "common/Aliases.hpp" -#include "common/Outcome.hpp" -#include "messages/SharedMessageBuilder.hpp" - -#include -#include -#include - -namespace chatterino { - -struct Emote; -using EmotePtr = std::shared_ptr; - -class Channel; - -class IrcMessageBuilder : public SharedMessageBuilder -{ -public: - IrcMessageBuilder() = delete; - - explicit IrcMessageBuilder(Channel *_channel, - const Communi::IrcPrivateMessage *_ircMessage, - const MessageParseArgs &_args); - explicit IrcMessageBuilder(Channel *_channel, - const Communi::IrcMessage *_ircMessage, - const MessageParseArgs &_args, QString content, - bool isAction); - - /** - * @brief used for global notice messages (i.e. notice messages without a channel as its target) - **/ - explicit IrcMessageBuilder(const Communi::IrcNoticeMessage *_ircMessage, - const MessageParseArgs &_args); - - /** - * @brief used for whisper messages (i.e. PRIVMSG messages with our nick as the target) - **/ - explicit IrcMessageBuilder(const Communi::IrcPrivateMessage *_ircMessage, - const MessageParseArgs &_args); - - MessagePtr build() override; - -private: - void appendUsername(); - - /** - * @brief holds the name of the target for the private/direct IRC message - * - * This might not be our nick - */ - QString whisperTarget_; -}; - -} // namespace chatterino diff --git a/src/providers/irc/IrcServer.cpp b/src/providers/irc/IrcServer.cpp deleted file mode 100644 index c88109ad439..00000000000 --- a/src/providers/irc/IrcServer.cpp +++ /dev/null @@ -1,386 +0,0 @@ -#include "providers/irc/IrcServer.hpp" - -#include "Application.hpp" -#include "common/QLogging.hpp" -#include "messages/Message.hpp" -#include "messages/MessageColor.hpp" -#include "messages/MessageElement.hpp" -#include "providers/irc/Irc2.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcMessageBuilder.hpp" -#include "providers/twitch/TwitchIrcServer.hpp" // NOTE: Included to access the mentions channel -#include "singletons/Settings.hpp" -#include "util/IrcHelpers.hpp" - -#include -#include - -#include -#include - -namespace chatterino { - -IrcServer::IrcServer(const IrcServerData &data) - : data_(new IrcServerData(data)) -{ - this->initializeIrc(); - - this->connect(); -} - -IrcServer::IrcServer(const IrcServerData &data, - const std::vector> &restoreChannels) - : IrcServer(data) -{ - for (auto &&weak : restoreChannels) - { - if (auto shared = weak.lock()) - { - this->channels[shared->getName()] = weak; - } - } -} - -IrcServer::~IrcServer() -{ - delete this->data_; -} - -int IrcServer::id() -{ - return this->data_->id; -} - -const QString &IrcServer::user() -{ - return this->data_->user; -} - -const QString &IrcServer::nick() -{ - return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick; -} - -const QString &IrcServer::userFriendlyIdentifier() -{ - return this->data_->host; -} - -void IrcServer::initializeConnectionSignals(IrcConnection *connection, - ConnectionType type) -{ - assert(type == Both); - - QObject::connect( - connection, &Communi::IrcConnection::socketError, this, - [this](QAbstractSocket::SocketError error) { - static int index = - QAbstractSocket::staticMetaObject.indexOfEnumerator( - "SocketError"); - - std::lock_guard lock(this->channelMutex); - - for (auto &&weak : this->channels) - { - if (auto shared = weak.lock()) - { - shared->addSystemMessage( - QStringLiteral("Socket error: ") + - QAbstractSocket::staticMetaObject.enumerator(index) - .valueToKey(error)); - } - } - }); - - QObject::connect(connection, &Communi::IrcConnection::nickNameRequired, - this, [](const QString &reserved, QString *result) { - *result = QString("%1%2").arg( - reserved, QString::number(std::rand() % 100)); - }); - - QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived, - this, [this](Communi::IrcNoticeMessage *message) { - MessageParseArgs args; - args.isReceivedWhisper = true; - - IrcMessageBuilder builder(message, args); - - auto msg = builder.build(); - - for (auto &&weak : this->channels) - { - if (auto shared = weak.lock()) - { - shared->addMessage(msg, - MessageContext::Original); - } - } - }); - QObject::connect(connection, - &Communi::IrcConnection::capabilityMessageReceived, this, - [this](Communi::IrcCapabilityMessage *message) { - const QStringList caps = message->capabilities(); - if (caps.contains("echo-message")) - { - this->hasEcho_ = true; - } - }); -} - -void IrcServer::initializeConnection(IrcConnection *connection, - ConnectionType type) -{ - assert(type == Both); - - connection->setSecure(this->data_->ssl); - connection->setHost(this->data_->host); - connection->setPort(this->data_->port); - - connection->setUserName(this->data_->user); - connection->setNickName(this->data_->nick.isEmpty() ? this->data_->user - : this->data_->nick); - connection->setRealName(this->data_->real.isEmpty() ? this->data_->user - : this->data_->nick); - connection->network()->setRequestedCapabilities({"echo-message"}); - - if (getSettings()->enableExperimentalIrc) - { - switch (this->data_->authType) - { - case IrcAuthType::Sasl: - connection->setSaslMechanism("PLAIN"); - [[fallthrough]]; - case IrcAuthType::Pass: - this->data_->getPassword( - this, [conn = new QPointer(connection) /* can't copy */, - this](const QString &password) mutable { - if (*conn) - { - (*conn)->setPassword(password); - this->open(Both); - } - - delete conn; - }); - break; - default: - this->open(Both); - } - } -} - -std::shared_ptr IrcServer::createChannel(const QString &channelName) -{ - return std::make_shared(channelName, this); -} - -bool IrcServer::hasSeparateWriteConnection() const -{ - return false; -} - -void IrcServer::onReadConnected(IrcConnection *connection) -{ - { - std::lock_guard lock(this->channelMutex); - - for (auto &&command : this->data_->connectCommands) - { - connection->sendRaw(command + "\r\n"); - } - } - - AbstractIrcServer::onReadConnected(connection); -} - -void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message) -{ - // Note: This doesn't use isPrivate() because it only applies to messages targeting our user, - // Servers or bouncers may send messages which have our user as the source - // (like with echo-message CAP), we need to take care of this. - if (!message->target().startsWith("#")) - { - MessageParseArgs args; - if (message->isOwn()) - { - // The server sent us a whisper which has our user as the source - args.isSentWhisper = true; - } - else - { - args.isReceivedWhisper = true; - } - - IrcMessageBuilder builder(message, args); - - auto msg = builder.build(); - - for (auto &&weak : this->channels) - { - if (auto shared = weak.lock()) - { - shared->addMessage(msg, MessageContext::Original); - } - } - return; - } - - auto target = message->target(); - target = target.startsWith('#') ? target.mid(1) : target; - - if (auto channel = this->getChannelOrEmpty(target); !channel->isEmpty()) - { - MessageParseArgs args; - IrcMessageBuilder builder(channel.get(), message, args); - - if (!builder.isIgnored()) - { - auto msg = builder.build(); - - channel->addMessage(msg, MessageContext::Original); - builder.triggerHighlights(); - const auto highlighted = msg->flags.has(MessageFlag::Highlighted); - const auto showInMentions = - msg->flags.has(MessageFlag::ShowInMentions); - - if (highlighted && showInMentions) - { - getApp()->getTwitch()->getMentionsChannel()->addMessage( - msg, MessageContext::Original); - } - } - else - { - qCDebug(chatterinoIrc) << "message ignored :rage:"; - } - } -} - -void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message) -{ - AbstractIrcServer::readConnectionMessageReceived(message); - - switch (message->type()) - { - case Communi::IrcMessage::Join: { - auto *x = static_cast(message); - - if (auto it = this->channels.find(x->channel()); - it != this->channels.end()) - { - if (auto shared = it->lock()) - { - if (message->nick() == this->data_->nick) - { - shared->addSystemMessage("joined"); - } - else - { - if (auto *c = - dynamic_cast(shared.get())) - { - c->addJoinedUser(x->nick()); - } - } - } - } - return; - } - - case Communi::IrcMessage::Part: { - auto *x = static_cast(message); - - if (auto it = this->channels.find(x->channel()); - it != this->channels.end()) - { - if (auto shared = it->lock()) - { - if (message->nick() == this->data_->nick) - { - shared->addSystemMessage("parted"); - } - else - { - if (auto *c = - dynamic_cast(shared.get())) - { - c->addPartedUser(x->nick()); - } - } - } - } - return; - } - - case Communi::IrcMessage::Pong: - case Communi::IrcMessage::Notice: - case Communi::IrcMessage::Private: - return; - - default: - if (getSettings()->showUnhandledIrcMessages) - { - MessageBuilder builder; - - builder.emplace( - calculateMessageTime(message).time()); - builder.emplace(message->toData(), - MessageElementFlag::Text); - builder->flags.set(MessageFlag::Debug); - - auto msg = builder.release(); - - for (auto &&weak : this->channels) - { - if (auto shared = weak.lock()) - { - shared->addMessage(msg, MessageContext::Original); - } - } - }; - } -} - -void IrcServer::sendWhisper(const QString &target, const QString &message) -{ - this->sendRawMessage(QString("PRIVMSG %1 :%2").arg(target, message)); - if (this->hasEcho()) - { - return; - } - - MessageParseArgs args; - args.isSentWhisper = true; - - MessageBuilder b; - - b.emplace(); - b.emplace(this->nick(), MessageElementFlag::Text, - MessageColor::Text, FontStyle::ChatMediumBold); - b.emplace("->", MessageElementFlag::Text, - MessageColor::System); - b.emplace(target + ":", MessageElementFlag::Text, - MessageColor::Text, FontStyle::ChatMediumBold); - b.emplace(message, MessageElementFlag::Text); - - auto msg = b.release(); - for (auto &&weak : this->channels) - { - if (auto shared = weak.lock()) - { - shared->addMessage(msg, MessageContext::Original); - } - } -} - -void IrcServer::sendRawMessage(const QString &rawMessage) -{ - AbstractIrcServer::sendRawMessage(rawMessage.left(510)); -} - -bool IrcServer::hasEcho() const -{ - return this->hasEcho_; -} - -} // namespace chatterino diff --git a/src/providers/irc/IrcServer.hpp b/src/providers/irc/IrcServer.hpp deleted file mode 100644 index 199a9340ab2..00000000000 --- a/src/providers/irc/IrcServer.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "providers/irc/AbstractIrcServer.hpp" - -namespace chatterino { - -struct IrcServerData; - -class IrcServer : public AbstractIrcServer -{ -public: - explicit IrcServer(const IrcServerData &data); - IrcServer(const IrcServerData &data, - const std::vector> &restoreChannels); - ~IrcServer() override; - - int id(); - const QString &user(); - const QString &nick(); - const QString &userFriendlyIdentifier(); - - bool hasEcho() const; - /** - * @brief sends a whisper to the target user (PRIVMSG where a user is the target) - */ - void sendWhisper(const QString &target, const QString &message); - - void sendRawMessage(const QString &rawMessage) override; - - // AbstractIrcServer interface -protected: - void initializeConnectionSignals(IrcConnection *connection, - ConnectionType type) override; - void initializeConnection(IrcConnection *connection, - ConnectionType type) override; - std::shared_ptr createChannel(const QString &channelName) override; - bool hasSeparateWriteConnection() const override; - - void onReadConnected(IrcConnection *connection) override; - void privateMessageReceived(Communi::IrcPrivateMessage *message) override; - void readConnectionMessageReceived(Communi::IrcMessage *message) override; - -private: - // pointer so we don't have to circle include Irc2.hpp - IrcServerData *data_; - - bool hasEcho_{false}; -}; - -} // namespace chatterino diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 29f9cce010a..889ecb958d4 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -511,7 +511,6 @@ class Settings #ifdef Q_OS_LINUX BoolSetting useKeyring = {"/misc/useKeyring", true}; #endif - BoolSetting enableExperimentalIrc = {"/misc/experimentalIrc", false}; IntSetting startUpNotification = {"/misc/startUpNotification", 0}; QStringSetting currentVersion = {"/misc/currentVersion", ""}; @@ -548,14 +547,7 @@ class Settings true}; BoolSetting lockNotebookLayout = {"/misc/lockNotebookLayout", false}; - /// Debug - BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages", - false}; - /// UI - // Purely QOL settings are here (like last item in a list). - IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 0}; - IntSetting lastSelectIrcConn = {"/ui/lastSelectIrcConn", 0}; BoolSetting showSendButton = {"/ui/showSendButton", false}; diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index cb5630c2520..07cee5d8b78 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -5,9 +5,6 @@ #include "common/QLogging.hpp" #include "debug/AssertInGuiThread.hpp" #include "messages/MessageElement.hpp" -#include "providers/irc/Irc2.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Paths.hpp" #include "singletons/Settings.hpp" @@ -643,19 +640,6 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj) obj.insert("type", "live"); } break; - case Channel::Type::Irc: { - if (auto *ircChannel = - dynamic_cast(channel.get().get())) - { - obj.insert("type", "irc"); - if (ircChannel->server()) - { - obj.insert("server", ircChannel->server()->id()); - } - obj.insert("channel", ircChannel->getName()); - } - } - break; case Channel::Type::Misc: { obj.insert("type", "misc"); obj.insert("name", channel.get()->getName()); @@ -705,11 +689,6 @@ IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor) { return getApp()->getTwitch()->getAutomodChannel(); } - else if (descriptor.type_ == "irc") - { - return Irc::instance().getOrAddChannel(descriptor.server_, - descriptor.channelName_); - } else if (descriptor.type_ == "misc") { return getApp()->getTwitchAbstract()->getChannelOrEmpty( diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp index f9869ce4d8b..6bb6b6fc31f 100644 --- a/src/util/StreamLink.cpp +++ b/src/util/StreamLink.cpp @@ -3,7 +3,6 @@ #include "Application.hpp" #include "common/QLogging.hpp" #include "common/Version.hpp" -#include "providers/irc/IrcMessageBuilder.hpp" #include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" diff --git a/src/widgets/dialogs/IrcConnectionEditor.cpp b/src/widgets/dialogs/IrcConnectionEditor.cpp deleted file mode 100644 index 8ebfd8f9552..00000000000 --- a/src/widgets/dialogs/IrcConnectionEditor.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "widgets/dialogs/IrcConnectionEditor.hpp" - -#include "ui_IrcConnectionEditor.h" - -namespace chatterino { - -IrcConnectionEditor::IrcConnectionEditor(const IrcServerData &data, bool isAdd, - QWidget *parent) - - : QDialog(parent, Qt::WindowStaysOnTopHint) - , ui_(new Ui::IrcConnectionEditor) - , data_(data) -{ - this->ui_->setupUi(this); - - this->setWindowTitle(QString(isAdd ? "Add " : "Edit ") + "Irc Connection"); - - QObject::connect(this->ui_->userNameLineEdit, &QLineEdit::textChanged, this, - [this](const QString &text) { - this->ui_->nickNameLineEdit->setPlaceholderText(text); - this->ui_->realNameLineEdit->setPlaceholderText(text); - }); - - this->ui_->serverLineEdit->setText(data.host); - this->ui_->portSpinBox->setValue(data.port); - this->ui_->securityCheckBox->setChecked(data.ssl); - this->ui_->userNameLineEdit->setText(data.user); - this->ui_->nickNameLineEdit->setText(data.nick); - this->ui_->realNameLineEdit->setText(data.real); - this->ui_->connectCommandsEditor->setPlainText( - data.connectCommands.join('\n')); - - data.getPassword(this, [this](const QString &password) { - this->ui_->passwordLineEdit->setText(password); - }); - - this->ui_->loginMethodComboBox->setCurrentIndex([&] { - switch (data.authType) - { - case IrcAuthType::Custom: - return 1; - case IrcAuthType::Pass: - return 2; - case IrcAuthType::Sasl: - return 3; - default: - return 0; - } - }()); - - QObject::connect(this->ui_->loginMethodComboBox, - qOverload(&QComboBox::currentIndexChanged), this, - [this](int index) { - if (index == 1) // Custom - { - this->ui_->connectCommandsEditor->setFocus(); - } - }); - - QFont font("Monospace"); - font.setStyleHint(QFont::TypeWriter); - this->ui_->connectCommandsEditor->setFont(font); -} - -IrcConnectionEditor::~IrcConnectionEditor() -{ - delete ui_; -} - -IrcServerData IrcConnectionEditor::data() -{ - auto data = this->data_; - data.host = this->ui_->serverLineEdit->text(); - data.port = this->ui_->portSpinBox->value(); - data.ssl = this->ui_->securityCheckBox->isChecked(); - data.user = this->ui_->userNameLineEdit->text(); - data.nick = this->ui_->nickNameLineEdit->text(); - data.real = this->ui_->realNameLineEdit->text(); - data.connectCommands = - this->ui_->connectCommandsEditor->toPlainText().split('\n'); - data.setPassword(this->ui_->passwordLineEdit->text()); - data.authType = [this] { - switch (this->ui_->loginMethodComboBox->currentIndex()) - { - case 1: - return IrcAuthType::Custom; - case 2: - return IrcAuthType::Pass; - case 3: - return IrcAuthType::Sasl; - default: - return IrcAuthType::Anonymous; - } - }(); - return data; -} - -} // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.hpp b/src/widgets/dialogs/IrcConnectionEditor.hpp deleted file mode 100644 index 9333f8830b8..00000000000 --- a/src/widgets/dialogs/IrcConnectionEditor.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "providers/irc/Irc2.hpp" -#include "widgets/BaseWindow.hpp" - -#include - -namespace Ui { - -class IrcConnectionEditor; - -} // namespace Ui - -namespace chatterino { - -struct IrcServerData; - -class IrcConnectionEditor : public QDialog -{ - Q_OBJECT - -public: - explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false, - QWidget *parent = nullptr); - IrcConnectionEditor(const IrcConnectionEditor &) = delete; - IrcConnectionEditor(IrcConnectionEditor &&) = delete; - IrcConnectionEditor &operator=(const IrcConnectionEditor &) = delete; - IrcConnectionEditor &operator=(IrcConnectionEditor &&) = delete; - ~IrcConnectionEditor() override; - - IrcServerData data(); - -private: - Ui::IrcConnectionEditor *ui_; - IrcServerData data_; -}; - -} // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.ui b/src/widgets/dialogs/IrcConnectionEditor.ui deleted file mode 100644 index 516fe2b5548..00000000000 --- a/src/widgets/dialogs/IrcConnectionEditor.ui +++ /dev/null @@ -1,261 +0,0 @@ - - - IrcConnectionEditor - - - - 0 - 0 - 329 - 414 - - - - Dialog - - - - - - - - Host: - - - - - - - irc.example.com - - - - - - - Port: - - - - - - - 65636 - - - 6697 - - - - - - - SSL: - - - - - - - true - - - true - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - User Name: - - - - - - - - - - Nick Name: - - - - - - - - - - Real Name: - - - - - - - - - - Login method: - - - - - - - Password: - - - - - - - QLineEdit::Password - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - - Anonymous - - - - - Custom - - - - - Server Password (/PASS $password) - - - - - SASL - - - - - - - - Send IRC commands -on connect: - - - Qt::PlainText - - - - - - - - 0 - 1 - - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - serverLineEdit - portSpinBox - securityCheckBox - userNameLineEdit - nickNameLineEdit - realNameLineEdit - loginMethodComboBox - passwordLineEdit - - - - - buttonBox - accepted() - IrcConnectionEditor - accept() - - - 254 - 248 - - - 157 - 274 - - - - - buttonBox - rejected() - IrcConnectionEditor - reject() - - - 254 - 248 - - - 286 - 274 - - - - - diff --git a/src/widgets/dialogs/SelectChannelDialog.cpp b/src/widgets/dialogs/SelectChannelDialog.cpp index 728caac8b3f..559ebde0e31 100644 --- a/src/widgets/dialogs/SelectChannelDialog.cpp +++ b/src/widgets/dialogs/SelectChannelDialog.cpp @@ -3,14 +3,10 @@ #include "Application.hpp" #include "common/QLogging.hpp" #include "controllers/hotkeys/HotkeyController.hpp" -#include "providers/irc/Irc2.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" #include "providers/twitch/TwitchIrcServer.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "util/LayoutCreator.hpp" -#include "widgets/dialogs/IrcConnectionEditor.hpp" #include "widgets/helper/EditableModelView.hpp" #include "widgets/helper/NotebookTab.hpp" #include "widgets/Notebook.hpp" @@ -28,7 +24,6 @@ namespace chatterino { constexpr int TAB_TWITCH = 0; -constexpr int TAB_IRC = 1; SelectChannelDialog::SelectChannelDialog(QWidget *parent) : BaseWindow( @@ -175,76 +170,6 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) tab->setCustomTitle("Twitch"); } - // irc - { - LayoutCreator obj(new QWidget()); - auto outerBox = obj.setLayoutType(); - - { - auto *view = this->ui_.irc.servers = - new EditableModelView(Irc::instance().newConnectionModel(this)); - - view->setTitles({"host", "port", "ssl", "user", "nick", "real", - "password", "login command"}); - view->getTableView()->horizontalHeader()->resizeSection(0, 140); - - view->getTableView()->horizontalHeader()->setSectionHidden(1, true); - view->getTableView()->horizontalHeader()->setSectionHidden(2, true); - view->getTableView()->horizontalHeader()->setSectionHidden(4, true); - view->getTableView()->horizontalHeader()->setSectionHidden(5, true); - - // We can safely ignore this signal's connection since the button won't be - // accessible after this dialog is closed - std::ignore = view->addButtonPressed.connect([] { - auto unique = IrcServerData{}; - unique.id = Irc::instance().uniqueId(); - - auto *editor = new IrcConnectionEditor(unique); - if (editor->exec() == QDialog::Accepted) - { - Irc::instance().connections.append(editor->data()); - } - }); - - QObject::connect( - view->getTableView(), &QTableView::doubleClicked, - [](const QModelIndex &index) { - auto *editor = new IrcConnectionEditor( - Irc::instance().connections.raw()[size_t(index.row())]); - - if (editor->exec() == QDialog::Accepted) - { - auto data = editor->data(); - auto &&conns = Irc::instance().connections.raw(); - int i = 0; - for (auto &&conn : conns) - { - if (conn.id == data.id) - { - Irc::instance().connections.removeAt( - i, Irc::noEraseCredentialCaller); - Irc::instance().connections.insert(data, i); - } - i++; - } - } - }); - - outerBox->addRow("Server:", view); - } - - outerBox->addRow("Channel: #", this->ui_.irc.channel = new QLineEdit); - - auto *tab = notebook->addPage(obj.getElement()); - tab->setCustomTitle("Irc (Beta)"); - - if (!getSettings()->enableExperimentalIrc) - { - tab->setEnable(false); - tab->setVisible(false); - } - } - layout->setStretchFactor(notebook.getElement(), 1); auto buttons = @@ -265,29 +190,11 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent) this->ui_.notebook->selectIndex(TAB_TWITCH); this->ui_.twitch.channel->setFocus(); - // restore ui state - // fourtf: enable when releasing irc - if (getSettings()->enableExperimentalIrc) - { - this->ui_.notebook->selectIndex(getSettings()->lastSelectChannelTab); - } - this->addShortcuts(); - - this->ui_.irc.servers->getTableView()->selectRow( - getSettings()->lastSelectIrcConn); } void SelectChannelDialog::ok() { - // save ui state - getSettings()->lastSelectChannelTab = - this->ui_.notebook->getSelectedIndex(); - getSettings()->lastSelectIrcConn = this->ui_.irc.servers->getTableView() - ->selectionModel() - ->currentIndex() - .row(); - // accept and close this->hasSelectedChannel_ = true; this->close(); @@ -334,31 +241,6 @@ void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel) this->ui_.twitch.automod->setFocus(); } break; - case Channel::Type::Irc: { - this->ui_.notebook->selectIndex(TAB_IRC); - this->ui_.irc.channel->setText(_channel.get()->getName()); - - if (auto *ircChannel = - dynamic_cast(_channel.get().get())) - { - if (auto *server = ircChannel->server()) - { - int i = 0; - for (auto &&conn : Irc::instance().connections) - { - if (conn.id == server->id()) - { - this->ui_.irc.servers->getTableView()->selectRow(i); - break; - } - i++; - } - } - } - - this->ui_.irc.channel->setFocus(); - } - break; default: { this->ui_.notebook->selectIndex(TAB_TWITCH); this->ui_.twitch.channel->setFocus(); @@ -405,25 +287,6 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const } } break; - case TAB_IRC: { - int row = this->ui_.irc.servers->getTableView() - ->selectionModel() - ->currentIndex() - .row(); - - auto &&vector = Irc::instance().connections.raw(); - - if (row >= 0 && row < int(vector.size())) - { - return Irc::instance().getOrAddChannel( - vector[size_t(row)].id, this->ui_.irc.channel->text()); - } - else - { - return Channel::getEmpty(); - } - } - //break; } return this->selectedChannel_; @@ -559,60 +422,9 @@ void SelectChannelDialog::addShortcuts() {"scrollPage", nullptr}, {"search", nullptr}, {"delete", nullptr}, + {"openTab", nullptr}, }; - if (getSettings()->enableExperimentalIrc) - { - actions.insert( - {"openTab", [this](std::vector arguments) -> QString { - if (arguments.size() == 0) - { - qCWarning(chatterinoHotkeys) - << "openTab shortcut called without arguments. " - "Takes only " - "one argument: tab specifier"; - return "openTab shortcut called without arguments. " - "Takes only one argument: tab specifier"; - } - auto target = arguments.at(0); - if (target == "last") - { - this->ui_.notebook->selectLastTab(); - } - else if (target == "next") - { - this->ui_.notebook->selectNextTab(); - } - else if (target == "previous") - { - this->ui_.notebook->selectPreviousTab(); - } - else - { - bool ok; - int result = target.toInt(&ok); - if (ok) - { - this->ui_.notebook->selectIndex(result); - } - else - { - qCWarning(chatterinoHotkeys) - << "Invalid argument for openTab shortcut"; - return QString("Invalid argument for openTab " - "shortcut: \"%1\". Use \"last\", " - "\"next\", \"previous\" or an integer.") - .arg(target); - } - } - return ""; - }}); - } - else - { - actions.emplace("openTab", nullptr); - } - this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory( HotkeyCategory::PopupWindow, actions, this); } diff --git a/src/widgets/dialogs/SelectChannelDialog.hpp b/src/widgets/dialogs/SelectChannelDialog.hpp index 65b2cb56b18..4fb34b508ec 100644 --- a/src/widgets/dialogs/SelectChannelDialog.hpp +++ b/src/widgets/dialogs/SelectChannelDialog.hpp @@ -51,10 +51,6 @@ class SelectChannelDialog final : public BaseWindow QRadioButton *live; QRadioButton *automod; } twitch; - struct { - QLineEdit *channel; - EditableModelView *servers; - } irc; } ui_; EventFilter tabFilter_; diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index a25cf7633a2..6ddb8758633 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -752,8 +752,7 @@ void UserInfoPopup::setData(const QString &name, auto type = this->channel_->getType(); if (type == Channel::Type::TwitchLive || - type == Channel::Type::TwitchWhispers || type == Channel::Type::Irc || - type == Channel::Type::Misc) + type == Channel::Type::TwitchWhispers || type == Channel::Type::Misc) { // not a normal twitch channel, the url opened by the button will be invalid, so hide the button this->ui_.usercardLabel->hide(); diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 522ab4ab2c1..9f410b9f9c4 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -2782,7 +2782,6 @@ bool ChannelView::mayContainMessage(const MessagePtr &message) case Channel::Type::Direct: case Channel::Type::Twitch: case Channel::Type::TwitchWatching: - case Channel::Type::Irc: // XXX: system messages may not have the channel set return message->flags.has(MessageFlag::System) || this->channel()->getName() == message->channelName; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 531abd0af5f..0b9f107ee25 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -1129,13 +1129,6 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addIntInput("Usercard scrollback limit (requires restart)", s.scrollbackUsercardLimit, 100, 100000, 100); - layout.addCheckbox("Enable experimental IRC support (requires restart)", - s.enableExperimentalIrc, false, - "When enabled, attempting to join a channel will " - "include an \"IRC (Beta)\" tab allowing the user to " - "connect to an IRC server outside of Twitch "); - layout.addCheckbox("Show unhandled IRC messages", - s.showUnhandledIrcMessages); layout.addDropdown( "Stack timeouts", {"Stack", "Stack until timeout", "Don't stack"}, s.timeoutStackStyle, diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index 17047cb84cd..c2ddc1160b2 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -5,8 +5,6 @@ #include "common/QLogging.hpp" #include "common/WindowDescriptors.hpp" #include "debug/AssertInGuiThread.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" #include "singletons/Fonts.hpp" #include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" @@ -817,22 +815,6 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively( SplitNodeDescriptor result; result.type_ = qmagicenum::enumNameString(channelType); - - switch (channelType) - { - case Channel::Type::Irc: { - if (auto *ircChannel = dynamic_cast( - currentNode->split_->getChannel().get())) - { - if (ircChannel->server()) - { - result.server_ = ircChannel->server()->id(); - } - } - } - break; - } - result.channelName_ = currentNode->split_->getChannel()->getName(); result.filters_ = currentNode->split_->getFilters(); return result; From 498ff41e9fc7a1fb6e43fedadd7f1eda688aa8ed Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 13:03:26 +0200 Subject: [PATCH 3/6] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d4c7ffcc28..2a6f5d87e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Minor: Replying to a message will now display the message being replied to. (#4350, #5519) - Minor: Links can now have prefixes and suffixes such as parentheses. (#5486, #5515) - Minor: Added support for scrolling in splits with touchscreen panning gestures. (#5524) +- Minor: Removed experimental IRC support. (#5547) - Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426) - Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378) - Bugfix: Fixed restricted users usernames not being clickable. (#5405) From 99fb4eddfc9afe74d416003ab9c6990d5f29d5ba Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 13:21:24 +0200 Subject: [PATCH 4/6] destruct SeventvEventAPI in source file? --- src/providers/seventv/SeventvEventAPI.cpp | 2 ++ src/providers/seventv/SeventvEventAPI.hpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/providers/seventv/SeventvEventAPI.cpp b/src/providers/seventv/SeventvEventAPI.cpp index c38d2a22380..0bdd1a0b5ef 100644 --- a/src/providers/seventv/SeventvEventAPI.cpp +++ b/src/providers/seventv/SeventvEventAPI.cpp @@ -26,6 +26,8 @@ SeventvEventAPI::SeventvEventAPI( { } +SeventvEventAPI::~SeventvEventAPI() = default; + void SeventvEventAPI::subscribeUser(const QString &userID, const QString &emoteSetID) { diff --git a/src/providers/seventv/SeventvEventAPI.hpp b/src/providers/seventv/SeventvEventAPI.hpp index 6a482731897..335ede3a184 100644 --- a/src/providers/seventv/SeventvEventAPI.hpp +++ b/src/providers/seventv/SeventvEventAPI.hpp @@ -33,6 +33,8 @@ class SeventvEventAPI std::chrono::milliseconds defaultHeartbeatInterval = std::chrono::milliseconds(25000)); + ~SeventvEventAPI() override; + struct { Signal emoteAdded; Signal emoteUpdated; From e1770aa6da60515df661a6657207f846f34cea8e Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 13:33:59 +0200 Subject: [PATCH 5/6] don't forward-declare seventv eventapi subscription --- src/providers/seventv/SeventvEventAPI.cpp | 2 -- src/providers/seventv/SeventvEventAPI.hpp | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/providers/seventv/SeventvEventAPI.cpp b/src/providers/seventv/SeventvEventAPI.cpp index 0bdd1a0b5ef..c38d2a22380 100644 --- a/src/providers/seventv/SeventvEventAPI.cpp +++ b/src/providers/seventv/SeventvEventAPI.cpp @@ -26,8 +26,6 @@ SeventvEventAPI::SeventvEventAPI( { } -SeventvEventAPI::~SeventvEventAPI() = default; - void SeventvEventAPI::subscribeUser(const QString &userID, const QString &emoteSetID) { diff --git a/src/providers/seventv/SeventvEventAPI.hpp b/src/providers/seventv/SeventvEventAPI.hpp index 335ede3a184..de2e23b33b0 100644 --- a/src/providers/seventv/SeventvEventAPI.hpp +++ b/src/providers/seventv/SeventvEventAPI.hpp @@ -2,6 +2,7 @@ #include "providers/liveupdates/BasicPubSubClient.hpp" #include "providers/liveupdates/BasicPubSubManager.hpp" +#include "providers/seventv/eventapi/Subscription.hpp" #include "util/QStringHash.hpp" #include @@ -9,7 +10,6 @@ namespace chatterino { namespace seventv::eventapi { - struct Subscription; struct Dispatch; struct EmoteAddDispatch; struct EmoteUpdateDispatch; @@ -33,8 +33,6 @@ class SeventvEventAPI std::chrono::milliseconds defaultHeartbeatInterval = std::chrono::milliseconds(25000)); - ~SeventvEventAPI() override; - struct { Signal emoteAdded; Signal emoteUpdated; From f6a4a03e67e3d2e4d044373a33a4da75ac320647 Mon Sep 17 00:00:00 2001 From: Rasmus Karlsson Date: Sat, 17 Aug 2024 13:36:19 +0200 Subject: [PATCH 6/6] add missing return for mock twitchircserver --- mocks/include/mocks/TwitchIrcServer.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mocks/include/mocks/TwitchIrcServer.hpp b/mocks/include/mocks/TwitchIrcServer.hpp index 97cb079add5..bbeba8ca47b 100644 --- a/mocks/include/mocks/TwitchIrcServer.hpp +++ b/mocks/include/mocks/TwitchIrcServer.hpp @@ -37,11 +37,13 @@ class MockTwitchIrcServer : public ITwitchIrcServer ChannelPtr getOrAddChannel(const QString &dirtyChannelName) override { assert(false && "unimplemented getOrAddChannel in mock irc server"); + return {}; } ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) override { assert(false && "unimplemented getChannelOrEmpty in mock irc server"); + return {}; } void addFakeMessage(const QString &data) override