mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Remove experimental IRC support (#5547)
This commit is contained in:
parent
cc8bd538b9
commit
998920d244
44 changed files with 478 additions and 2841 deletions
|
@ -24,6 +24,7 @@
|
||||||
- Minor: Replying to a message will now display the message being replied to. (#4350, #5519)
|
- 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: 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: 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: 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: 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)
|
- Bugfix: Fixed restricted users usernames not being clickable. (#5405)
|
||||||
|
|
|
@ -133,7 +133,7 @@ public:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
IAbstractIrcServer *getTwitchAbstract() override
|
ITwitchIrcServer *getTwitchAbstract() override
|
||||||
{
|
{
|
||||||
assert(false && "EmptyApplication::getTwitchAbstract was called "
|
assert(false && "EmptyApplication::getTwitchAbstract was called "
|
||||||
"without being initialized");
|
"without being initialized");
|
||||||
|
|
|
@ -26,6 +26,38 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void connect() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRawMessage(const QString &rawMessage) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void addGlobalSystemMessage(const QString &messageText) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void forEachChannel(std::function<void(ChannelPtr)> func) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void forEachChannelAndSpecialChannels(
|
void forEachChannelAndSpecialChannels(
|
||||||
std::function<void(ChannelPtr)> func) override
|
std::function<void(ChannelPtr)> func) override
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "controllers/sound/ISoundController.hpp"
|
#include "controllers/sound/ISoundController.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
#include "providers/irc/AbstractIrcServer.hpp"
|
|
||||||
#include "providers/links/LinkResolver.hpp"
|
#include "providers/links/LinkResolver.hpp"
|
||||||
#include "providers/seventv/SeventvAPI.hpp"
|
#include "providers/seventv/SeventvAPI.hpp"
|
||||||
#include "providers/seventv/SeventvEmotes.hpp"
|
#include "providers/seventv/SeventvEmotes.hpp"
|
||||||
|
@ -33,7 +32,6 @@
|
||||||
#include "providers/bttv/BttvLiveUpdates.hpp"
|
#include "providers/bttv/BttvLiveUpdates.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/ffz/FfzBadges.hpp"
|
#include "providers/ffz/FfzBadges.hpp"
|
||||||
#include "providers/irc/Irc2.hpp"
|
|
||||||
#include "providers/seventv/eventapi/Dispatch.hpp"
|
#include "providers/seventv/eventapi/Dispatch.hpp"
|
||||||
#include "providers/seventv/eventapi/Subscription.hpp"
|
#include "providers/seventv/eventapi/Subscription.hpp"
|
||||||
#include "providers/seventv/SeventvBadges.hpp"
|
#include "providers/seventv/SeventvBadges.hpp"
|
||||||
|
@ -224,11 +222,6 @@ void Application::initialize(Settings &settings, const Paths &paths)
|
||||||
if (!this->args_.isFramelessEmbed)
|
if (!this->args_.isFramelessEmbed)
|
||||||
{
|
{
|
||||||
getSettings()->currentVersion.setValue(CHATTERINO_VERSION);
|
getSettings()->currentVersion.setValue(CHATTERINO_VERSION);
|
||||||
|
|
||||||
if (getSettings()->enableExperimentalIrc)
|
|
||||||
{
|
|
||||||
Irc::instance().load();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->accounts->load();
|
this->accounts->load();
|
||||||
|
@ -546,7 +539,7 @@ ITwitchIrcServer *Application::getTwitch()
|
||||||
return this->twitch.get();
|
return this->twitch.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
IAbstractIrcServer *Application::getTwitchAbstract()
|
ITwitchIrcServer *Application::getTwitchAbstract()
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,6 @@ class SeventvEmotes;
|
||||||
class SeventvEventAPI;
|
class SeventvEventAPI;
|
||||||
class ILinkResolver;
|
class ILinkResolver;
|
||||||
class IStreamerMode;
|
class IStreamerMode;
|
||||||
class IAbstractIrcServer;
|
|
||||||
|
|
||||||
class IApplication
|
class IApplication
|
||||||
{
|
{
|
||||||
|
@ -84,7 +83,7 @@ public:
|
||||||
virtual HighlightController *getHighlights() = 0;
|
virtual HighlightController *getHighlights() = 0;
|
||||||
virtual NotificationController *getNotifications() = 0;
|
virtual NotificationController *getNotifications() = 0;
|
||||||
virtual ITwitchIrcServer *getTwitch() = 0;
|
virtual ITwitchIrcServer *getTwitch() = 0;
|
||||||
virtual IAbstractIrcServer *getTwitchAbstract() = 0;
|
virtual ITwitchIrcServer *getTwitchAbstract() = 0;
|
||||||
virtual PubSub *getTwitchPubSub() = 0;
|
virtual PubSub *getTwitchPubSub() = 0;
|
||||||
virtual ILogging *getChatLogger() = 0;
|
virtual ILogging *getChatLogger() = 0;
|
||||||
virtual IChatterinoBadges *getChatterinoBadges() = 0;
|
virtual IChatterinoBadges *getChatterinoBadges() = 0;
|
||||||
|
@ -195,7 +194,8 @@ public:
|
||||||
NotificationController *getNotifications() override;
|
NotificationController *getNotifications() override;
|
||||||
HighlightController *getHighlights() override;
|
HighlightController *getHighlights() override;
|
||||||
ITwitchIrcServer *getTwitch() override;
|
ITwitchIrcServer *getTwitch() override;
|
||||||
IAbstractIrcServer *getTwitchAbstract() override;
|
[[deprecated("use getTwitch()")]] ITwitchIrcServer *getTwitchAbstract()
|
||||||
|
override;
|
||||||
PubSub *getTwitchPubSub() override;
|
PubSub *getTwitchPubSub() override;
|
||||||
ILogging *getChatLogger() override;
|
ILogging *getChatLogger() override;
|
||||||
FfzBadges *getFfzBadges() override;
|
FfzBadges *getFfzBadges() override;
|
||||||
|
|
|
@ -339,22 +339,8 @@ set(SOURCE_FILES
|
||||||
providers/ffz/FfzUtil.cpp
|
providers/ffz/FfzUtil.cpp
|
||||||
providers/ffz/FfzUtil.hpp
|
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.cpp
|
||||||
providers/irc/IrcConnection2.hpp
|
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.cpp
|
||||||
providers/links/LinkInfo.hpp
|
providers/links/LinkInfo.hpp
|
||||||
|
@ -576,9 +562,6 @@ set(SOURCE_FILES
|
||||||
widgets/dialogs/EditHotkeyDialog.hpp
|
widgets/dialogs/EditHotkeyDialog.hpp
|
||||||
widgets/dialogs/EmotePopup.cpp
|
widgets/dialogs/EmotePopup.cpp
|
||||||
widgets/dialogs/EmotePopup.hpp
|
widgets/dialogs/EmotePopup.hpp
|
||||||
widgets/dialogs/IrcConnectionEditor.cpp
|
|
||||||
widgets/dialogs/IrcConnectionEditor.hpp
|
|
||||||
widgets/dialogs/IrcConnectionEditor.ui
|
|
||||||
widgets/dialogs/LastRunCrashDialog.cpp
|
widgets/dialogs/LastRunCrashDialog.cpp
|
||||||
widgets/dialogs/LastRunCrashDialog.hpp
|
widgets/dialogs/LastRunCrashDialog.hpp
|
||||||
widgets/dialogs/LoginDialog.cpp
|
widgets/dialogs/LoginDialog.cpp
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/irc/IrcChannel2.hpp"
|
|
||||||
#include "providers/irc/IrcServer.hpp"
|
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Logging.hpp"
|
#include "singletons/Logging.hpp"
|
||||||
|
@ -36,8 +34,6 @@ Channel::Channel(const QString &name, Type type)
|
||||||
{
|
{
|
||||||
this->platform_ = "twitch";
|
this->platform_ = "twitch";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Irc platform is set through IrcChannel2 ctor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel::~Channel()
|
Channel::~Channel()
|
||||||
|
|
|
@ -51,7 +51,6 @@ public:
|
||||||
TwitchLive,
|
TwitchLive,
|
||||||
TwitchAutomod,
|
TwitchAutomod,
|
||||||
TwitchEnd,
|
TwitchEnd,
|
||||||
Irc,
|
|
||||||
Misc,
|
Misc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,8 +182,6 @@ constexpr magic_enum::customize::customize_t
|
||||||
return "live";
|
return "live";
|
||||||
case Type::TwitchAutomod:
|
case Type::TwitchAutomod:
|
||||||
return "automod";
|
return "automod";
|
||||||
case Type::Irc:
|
|
||||||
return "irc";
|
|
||||||
case Type::Misc:
|
case Type::Misc:
|
||||||
return "misc";
|
return "misc";
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QColor>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
// Colors taken from https://modern.ircdocs.horse/formatting.html
|
|
||||||
static QMap<int, QColor> 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
|
|
|
@ -9,8 +9,6 @@
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.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/api/Helix.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
@ -242,17 +240,6 @@ QString sendWhisper(const CommandContext &ctx)
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// we must be on IRC
|
|
||||||
auto *ircChannel = dynamic_cast<IrcChannel *>(ctx.channel.get());
|
|
||||||
if (ircChannel == nullptr)
|
|
||||||
{
|
|
||||||
// give up
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *server = ircChannel->server();
|
|
||||||
server->sendWhisper(target, message);
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/IrcColors.hpp"
|
|
||||||
#include "common/LinkParser.hpp"
|
#include "common/LinkParser.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "messages/Image.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageColor.hpp"
|
#include "messages/MessageColor.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -12,18 +10,12 @@
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
#include "providers/twitch/PubSubActions.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
|
||||||
#include "singletons/Theme.hpp"
|
|
||||||
#include "util/FormatTime.hpp"
|
#include "util/FormatTime.hpp"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QRegularExpression IRC_COLOR_PARSE_REGEX(
|
|
||||||
"(\u0003(\\d{1,2})?(,(\\d{1,2}))?|\u000f)",
|
|
||||||
QRegularExpression::UseUnicodePropertiesOption);
|
|
||||||
|
|
||||||
QString formatUpdatedEmoteList(const QString &platform,
|
QString formatUpdatedEmoteList(const QString &platform,
|
||||||
const std::vector<QString> &emoteNames,
|
const std::vector<QString> &emoteNames,
|
||||||
bool isAdd, bool isFirstWord)
|
bool isAdd, bool isFirstWord)
|
||||||
|
@ -679,107 +671,6 @@ void MessageBuilder::addLink(const linkparser::Parsed &parsedLink,
|
||||||
getApp()->getLinkResolver()->resolve(el->linkInfo());
|
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)
|
void MessageBuilder::addTextOrEmoji(EmotePtr emote)
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(emote, MessageElementFlag::EmojiAll);
|
this->emplace<EmoteElement>(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,
|
TextElement *MessageBuilder::emplaceSystemTextAndUpdate(const QString &text,
|
||||||
QString &toUpdate)
|
QString &toUpdate)
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,13 +116,6 @@ public:
|
||||||
void append(std::unique_ptr<MessageElement> element);
|
void append(std::unique_ptr<MessageElement> element);
|
||||||
void addLink(const linkparser::Parsed &parsedLink, const QString &source);
|
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 <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// clang-format can be enabled once clang-format v11+ has been installed in CI
|
// clang-format can be enabled once clang-format v11+ has been installed in CI
|
||||||
|
@ -155,17 +148,6 @@ private:
|
||||||
TextElement *emplaceSystemTextAndUpdate(const QString &text,
|
TextElement *emplaceSystemTextAndUpdate(const QString &text,
|
||||||
QString &toUpdate);
|
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> message_;
|
std::shared_ptr<Message> message_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 <QCoreApplication>
|
|
||||||
|
|
||||||
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<std::mutex> lock(this->connectionMutex_);
|
|
||||||
|
|
||||||
if (type == Write)
|
|
||||||
{
|
|
||||||
this->writeConnection_->open();
|
|
||||||
}
|
|
||||||
if (type & Read)
|
|
||||||
{
|
|
||||||
this->readConnection_->open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::addGlobalSystemMessage(const QString &messageText)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
||||||
|
|
||||||
MessageBuilder b(systemMessage, messageText);
|
|
||||||
auto message = b.release();
|
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
||||||
{
|
|
||||||
std::shared_ptr<Channel> chan = weak.lock();
|
|
||||||
if (!chan)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
chan->addMessage(message, MessageContext::Original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::disconnect()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::weak_ptr<Channel>> AbstractIrcServer::getChannels()
|
|
||||||
{
|
|
||||||
std::lock_guard lock(this->channelMutex);
|
|
||||||
std::vector<std::weak_ptr<Channel>> 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<Channel> &weak : this->channels.values())
|
|
||||||
{
|
|
||||||
std::shared_ptr<Channel> chan = weak.lock();
|
|
||||||
if (!chan)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LimitedQueueSnapshot<MessagePtr> 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<std::mutex> lock(this->channelMutex);
|
|
||||||
|
|
||||||
MessageBuilder b(systemMessage, "disconnected");
|
|
||||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
|
||||||
auto disconnectedMsg = b.release();
|
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
||||||
{
|
|
||||||
std::shared_ptr<Channel> chan = weak.lock();
|
|
||||||
if (!chan)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
chan->addMessage(disconnectedMsg, MessageContext::Original);
|
|
||||||
|
|
||||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
||||||
{
|
|
||||||
channel->markDisconnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::markChannelsConnected()
|
|
||||||
{
|
|
||||||
this->forEachChannel([](const ChannelPtr &chan) {
|
|
||||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
||||||
{
|
|
||||||
channel->markConnected();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Channel> 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<Communi::IrcPrivateMessage *>(fakeMessage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->readConnectionMessageReceived(fakeMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::privateMessageReceived(
|
|
||||||
Communi::IrcPrivateMessage *message)
|
|
||||||
{
|
|
||||||
(void)message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::readConnectionMessageReceived(
|
|
||||||
Communi::IrcMessage *message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
||||||
{
|
|
||||||
ChannelPtr chan = weak.lock();
|
|
||||||
if (!chan)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
func(chan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -1,135 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
|
||||||
#include "providers/irc/IrcConnection2.hpp"
|
|
||||||
#include "util/RatelimitBucket.hpp"
|
|
||||||
|
|
||||||
#include <IrcMessage>
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class Channel;
|
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
|
||||||
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<void(ChannelPtr)> 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<std::weak_ptr<Channel>> 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<void(ChannelPtr)> 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<Channel> 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<Channel> getCustomChannel(
|
|
||||||
const QString &channelName);
|
|
||||||
|
|
||||||
virtual bool hasSeparateWriteConnection() const = 0;
|
|
||||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
|
||||||
|
|
||||||
void open(ConnectionType type);
|
|
||||||
|
|
||||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
|
||||||
std::mutex channelMutex;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void initConnection();
|
|
||||||
|
|
||||||
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
|
||||||
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
|
||||||
|
|
||||||
// Our rate limiting bucket for the Twitch join rate limits
|
|
||||||
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
|
||||||
QObjectPtr<RatelimitBucket> joinBucket_;
|
|
||||||
|
|
||||||
QTimer reconnectTimer_;
|
|
||||||
int falloffCounter_ = 1;
|
|
||||||
|
|
||||||
std::mutex connectionMutex_;
|
|
||||||
|
|
||||||
// bool autoReconnect_ = false;
|
|
||||||
pajlada::Signals::SignalHolder connections_;
|
|
||||||
|
|
||||||
bool initialized_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -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 <pajlada/serialize.hpp>
|
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
QString configPath()
|
|
||||||
{
|
|
||||||
return combinePath(getApp()->getPaths().settingsDirectory, "irc.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
class Model : public SignalVectorModel<IrcServerData>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Model(QObject *parent)
|
|
||||||
: SignalVectorModel<IrcServerData>(6, parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn a vector item into a model row
|
|
||||||
IrcServerData getItemFromRow(std::vector<QStandardItem *> &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<QStandardItem *> &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<void(const QString &)> &&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<IrcServer>(args.item, ab->second);
|
|
||||||
|
|
||||||
// set server of abandoned channels
|
|
||||||
for (auto weak : ab->second)
|
|
||||||
{
|
|
||||||
if (auto shared = weak.lock())
|
|
||||||
{
|
|
||||||
if (auto *ircChannel =
|
|
||||||
dynamic_cast<IrcChannel *>(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<IrcServer>(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<IrcChannel *>(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<IrcChannel>(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<int> 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
|
|
|
@ -1,71 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
|
|
||||||
#include <rapidjson/rapidjson.h>
|
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
class QAbstractTableModel;
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class Channel;
|
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
|
||||||
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<void(const QString &)> &&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<void *>(1);
|
|
||||||
|
|
||||||
SignalVector<IrcServerData> 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<int, std::unique_ptr<IrcServer>> servers_;
|
|
||||||
std::unordered_map<int, std::vector<std::weak_ptr<Channel>>>
|
|
||||||
abandonedChannels_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -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
|
|
|
@ -1,26 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
// 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
|
|
|
@ -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<Channel *>(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<TextElement>("#" + this->getName(),
|
|
||||||
MessageElementFlag::ChannelName,
|
|
||||||
MessageColor::System)
|
|
||||||
->setLink({Link::JumpToChannel, this->getName()});
|
|
||||||
|
|
||||||
auto now = QDateTime::currentDateTime();
|
|
||||||
builder.emplace<TimestampElement>(now.time());
|
|
||||||
builder.message().serverReceivedTime = now;
|
|
||||||
|
|
||||||
auto username = this->server()->nick();
|
|
||||||
builder
|
|
||||||
.emplace<TextElement>(
|
|
||||||
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
|
|
|
@ -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
|
|
|
@ -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<QString, QString>{
|
|
||||||
{"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 <channel> <client> [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
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Outcome.hpp"
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class IrcChannel;
|
|
||||||
|
|
||||||
Outcome invokeIrcCommand(const QString &command, const QString ¶ms,
|
|
||||||
IrcChannel &channel);
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -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<TimestampElement>(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<TextElement>(usernameText, MessageElementFlag::Username,
|
|
||||||
this->usernameColor_,
|
|
||||||
FontStyle::ChatMediumBold)
|
|
||||||
->setLink({Link::UserWhisper, this->message().displayName});
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
|
||||||
MessageColor::System, FontStyle::ChatMedium);
|
|
||||||
|
|
||||||
if (this->whisperTarget_.isEmpty())
|
|
||||||
{
|
|
||||||
this->emplace<TextElement>("you:", MessageElementFlag::Username);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->emplace<TextElement>(this->whisperTarget_ + ":",
|
|
||||||
MessageElementFlag::Username,
|
|
||||||
getRandomColor(this->whisperTarget_),
|
|
||||||
FontStyle::ChatMediumBold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this->args.isSentWhisper)
|
|
||||||
{
|
|
||||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
|
||||||
this->usernameColor_,
|
|
||||||
FontStyle::ChatMediumBold);
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
|
||||||
MessageColor::System, FontStyle::ChatMedium);
|
|
||||||
|
|
||||||
this->emplace<TextElement>(
|
|
||||||
this->whisperTarget_ + ":", MessageElementFlag::Username,
|
|
||||||
getRandomColor(this->whisperTarget_), FontStyle::ChatMediumBold)
|
|
||||||
->setLink({Link::UserWhisper, this->whisperTarget_});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!this->action_)
|
|
||||||
{
|
|
||||||
usernameText += ":";
|
|
||||||
}
|
|
||||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
|
||||||
this->usernameColor_,
|
|
||||||
FontStyle::ChatMediumBold)
|
|
||||||
->setLink({Link::UserInfo, this->message().loginName});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -1,56 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
|
||||||
#include "common/Outcome.hpp"
|
|
||||||
#include "messages/SharedMessageBuilder.hpp"
|
|
||||||
|
|
||||||
#include <IrcMessage>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct Emote;
|
|
||||||
using EmotePtr = std::shared_ptr<const Emote>;
|
|
||||||
|
|
||||||
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
|
|
|
@ -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 <QMetaEnum>
|
|
||||||
#include <QPointer>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
IrcServer::IrcServer(const IrcServerData &data)
|
|
||||||
: data_(new IrcServerData(data))
|
|
||||||
{
|
|
||||||
this->initializeIrc();
|
|
||||||
|
|
||||||
this->connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
IrcServer::IrcServer(const IrcServerData &data,
|
|
||||||
const std::vector<std::weak_ptr<Channel>> &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<Channel> IrcServer::createChannel(const QString &channelName)
|
|
||||||
{
|
|
||||||
return std::make_shared<IrcChannel>(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<Communi::IrcJoinMessage *>(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<ChannelChatters *>(shared.get()))
|
|
||||||
{
|
|
||||||
c->addJoinedUser(x->nick());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Communi::IrcMessage::Part: {
|
|
||||||
auto *x = static_cast<Communi::IrcPartMessage *>(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<ChannelChatters *>(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<TimestampElement>(
|
|
||||||
calculateMessageTime(message).time());
|
|
||||||
builder.emplace<TextElement>(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<TimestampElement>();
|
|
||||||
b.emplace<TextElement>(this->nick(), MessageElementFlag::Text,
|
|
||||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
|
||||||
b.emplace<TextElement>("->", MessageElementFlag::Text,
|
|
||||||
MessageColor::System);
|
|
||||||
b.emplace<TextElement>(target + ":", MessageElementFlag::Text,
|
|
||||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
|
||||||
b.emplace<TextElement>(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
|
|
|
@ -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<std::weak_ptr<Channel>> &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<Channel> 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
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "providers/liveupdates/BasicPubSubClient.hpp"
|
#include "providers/liveupdates/BasicPubSubClient.hpp"
|
||||||
#include "providers/liveupdates/BasicPubSubManager.hpp"
|
#include "providers/liveupdates/BasicPubSubManager.hpp"
|
||||||
|
#include "providers/seventv/eventapi/Subscription.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
@ -9,7 +10,6 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
namespace seventv::eventapi {
|
namespace seventv::eventapi {
|
||||||
struct Subscription;
|
|
||||||
struct Dispatch;
|
struct Dispatch;
|
||||||
struct EmoteAddDispatch;
|
struct EmoteAddDispatch;
|
||||||
struct EmoteUpdateDispatch;
|
struct EmoteUpdateDispatch;
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "messages/MessageColor.hpp"
|
#include "messages/MessageColor.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "messages/MessageThread.hpp"
|
#include "messages/MessageThread.hpp"
|
||||||
#include "providers/irc/AbstractIrcServer.hpp"
|
|
||||||
#include "providers/twitch/ChannelPointReward.hpp"
|
#include "providers/twitch/ChannelPointReward.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||||
|
@ -171,7 +170,7 @@ void updateReplyParticipatedStatus(const QVariantMap &tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelPtr channelOrEmptyByTarget(const QString &target,
|
ChannelPtr channelOrEmptyByTarget(const QString &target,
|
||||||
IAbstractIrcServer &server)
|
ITwitchIrcServer &server)
|
||||||
{
|
{
|
||||||
QString channelName;
|
QString channelName;
|
||||||
if (!trimChannelName(target, channelName))
|
if (!trimChannelName(target, channelName))
|
||||||
|
@ -679,10 +678,9 @@ std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
ITwitchIrcServer &twitchServer,
|
ITwitchIrcServer &twitchServer)
|
||||||
IAbstractIrcServer &abstractIrcServer)
|
|
||||||
{
|
{
|
||||||
auto chan = channelOrEmptyByTarget(message->target(), abstractIrcServer);
|
auto chan = channelOrEmptyByTarget(message->target(), twitchServer);
|
||||||
if (chan->isEmpty())
|
if (chan->isEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -988,9 +986,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *ircMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handleUserNoticeMessage(
|
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
Communi::IrcMessage *message, ITwitchIrcServer &twitchServer,
|
ITwitchIrcServer &twitchServer)
|
||||||
IAbstractIrcServer &abstractIrcServer)
|
|
||||||
{
|
{
|
||||||
auto tags = message->tags();
|
auto tags = message->tags();
|
||||||
auto parameters = message->parameters();
|
auto parameters = message->parameters();
|
||||||
|
@ -1003,7 +1000,7 @@ void IrcMessageHandler::handleUserNoticeMessage(
|
||||||
content = parameters[1];
|
content = parameters[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto chn = abstractIrcServer.getChannelOrEmpty(target);
|
auto chn = twitchServer.getChannelOrEmpty(target);
|
||||||
if (isIgnoredMessage({
|
if (isIgnoredMessage({
|
||||||
.message = content,
|
.message = content,
|
||||||
.twitchUserID = tags.value("user-id").toString(),
|
.twitchUserID = tags.value("user-id").toString(),
|
||||||
|
@ -1096,7 +1093,7 @@ void IrcMessageHandler::handleUserNoticeMessage(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto chan = abstractIrcServer.getChannelOrEmpty(channelName);
|
auto chan = twitchServer.getChannelOrEmpty(channelName);
|
||||||
|
|
||||||
if (!chan->isEmpty())
|
if (!chan->isEmpty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class IAbstractIrcServer;
|
|
||||||
class ITwitchIrcServer;
|
class ITwitchIrcServer;
|
||||||
class Channel;
|
class Channel;
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
using ChannelPtr = std::shared_ptr<Channel>;
|
||||||
|
@ -39,8 +38,7 @@ public:
|
||||||
std::vector<MessagePtr> &otherLoaded);
|
std::vector<MessagePtr> &otherLoaded);
|
||||||
|
|
||||||
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
ITwitchIrcServer &twitchServer,
|
ITwitchIrcServer &twitchServer);
|
||||||
IAbstractIrcServer &abstractIrcServer);
|
|
||||||
|
|
||||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||||
|
@ -50,8 +48,7 @@ public:
|
||||||
void handleWhisperMessage(Communi::IrcMessage *ircMessage);
|
void handleWhisperMessage(Communi::IrcMessage *ircMessage);
|
||||||
|
|
||||||
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
ITwitchIrcServer &twitchServer,
|
ITwitchIrcServer &twitchServer);
|
||||||
IAbstractIrcServer &abstractIrcServer);
|
|
||||||
|
|
||||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
|
||||||
#include "providers/IvrApi.hpp"
|
#include "providers/IvrApi.hpp"
|
||||||
#include "providers/seventv/SeventvAPI.hpp"
|
#include "providers/seventv/SeventvAPI.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
|
|
@ -2,35 +2,45 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/Common.hpp"
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
|
#include "messages/LimitedQueueSnapshot.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/bttv/BttvLiveUpdates.hpp"
|
|
||||||
#include "providers/ffz/FfzEmotes.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/SeventvEmotes.hpp"
|
||||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/ChannelPointReward.hpp"
|
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/Helpers.hpp"
|
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
#include "util/RatelimitBucket.hpp"
|
||||||
|
|
||||||
#include <IrcCommand>
|
#include <IrcCommand>
|
||||||
|
#include <IrcMessage>
|
||||||
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// Ratelimits for joinBucket_
|
||||||
|
constexpr int JOIN_RATELIMIT_BUDGET = 18;
|
||||||
|
constexpr int JOIN_RATELIMIT_COOLDOWN = 12500;
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
|
||||||
void sendHelixMessage(const std::shared_ptr<TwitchChannel> &channel,
|
void sendHelixMessage(const std::shared_ptr<TwitchChannel> &channel,
|
||||||
|
@ -140,12 +150,77 @@ TwitchIrcServer::TwitchIrcServer()
|
||||||
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
||||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
, 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());
|
||||||
|
|
||||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
// Apply a leaky bucket rate limiting to JOIN messages
|
||||||
// this->connect(); },
|
auto actuallyJoin = [&](QString message) {
|
||||||
// this->signalHolder_,
|
if (!this->channels.contains(message))
|
||||||
// false);
|
{
|
||||||
|
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 TwitchIrcServer::initialize()
|
void TwitchIrcServer::initialize()
|
||||||
|
@ -246,14 +321,12 @@ std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
||||||
void TwitchIrcServer::privateMessageReceived(
|
void TwitchIrcServer::privateMessageReceived(
|
||||||
Communi::IrcPrivateMessage *message)
|
Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
IrcMessageHandler::instance().handlePrivMessage(message, *this, *this);
|
IrcMessageHandler::instance().handlePrivMessage(message, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::readConnectionMessageReceived(
|
void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
Communi::IrcMessage *message)
|
Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
AbstractIrcServer::readConnectionMessageReceived(message);
|
|
||||||
|
|
||||||
if (message->type() == Communi::IrcMessage::Type::Private)
|
if (message->type() == Communi::IrcMessage::Type::Private)
|
||||||
{
|
{
|
||||||
// We already have a handler for private messages
|
// We already have a handler for private messages
|
||||||
|
@ -293,7 +366,7 @@ void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
}
|
}
|
||||||
else if (command == "USERNOTICE")
|
else if (command == "USERNOTICE")
|
||||||
{
|
{
|
||||||
handler.handleUserNoticeMessage(message, *this, *this);
|
handler.handleUserNoticeMessage(message, *this);
|
||||||
}
|
}
|
||||||
else if (command == "NOTICE")
|
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<Channel> &weak : this->channels.values())
|
||||||
|
{
|
||||||
|
std::shared_ptr<Channel> chan = weak.lock();
|
||||||
|
if (!chan)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LimitedQueueSnapshot<MessagePtr> 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<std::mutex> lock(this->channelMutex);
|
||||||
|
|
||||||
|
MessageBuilder b(systemMessage, "disconnected");
|
||||||
|
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||||
|
auto disconnectedMsg = b.release();
|
||||||
|
|
||||||
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||||
|
{
|
||||||
|
std::shared_ptr<Channel> chan = weak.lock();
|
||||||
|
if (!chan)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
chan->addMessage(disconnectedMsg, MessageContext::Original);
|
||||||
|
|
||||||
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||||
|
{
|
||||||
|
channel->markDisconnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
||||||
const QString &channelName)
|
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(
|
bool TwitchIrcServer::prepareToSend(
|
||||||
const std::shared_ptr<TwitchChannel> &channel)
|
const std::shared_ptr<TwitchChannel> &channel)
|
||||||
{
|
{
|
||||||
|
@ -779,4 +924,188 @@ void TwitchIrcServer::dropSeventvChannel(const QString &userID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TwitchIrcServer::markChannelsConnected()
|
||||||
|
{
|
||||||
|
this->forEachChannel([](const ChannelPtr &chan) {
|
||||||
|
if (auto *channel = dynamic_cast<TwitchChannel *>(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<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->readConnectionMessageReceived(fakeMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchIrcServer::addGlobalSystemMessage(const QString &messageText)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||||
|
|
||||||
|
MessageBuilder b(systemMessage, messageText);
|
||||||
|
auto message = b.release();
|
||||||
|
|
||||||
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||||
|
{
|
||||||
|
std::shared_ptr<Channel> chan = weak.lock();
|
||||||
|
if (!chan)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
chan->addMessage(message, MessageContext::Original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||||
|
|
||||||
|
for (std::weak_ptr<Channel> &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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(this->connectionMutex_);
|
||||||
|
|
||||||
|
if (type == ConnectionType::Write)
|
||||||
|
{
|
||||||
|
this->writeConnection_->open();
|
||||||
|
}
|
||||||
|
if (type == ConnectionType::Read)
|
||||||
|
{
|
||||||
|
this->readConnection_->open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -2,12 +2,18 @@
|
||||||
|
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "providers/irc/AbstractIrcServer.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "providers/irc/IrcConnection2.hpp"
|
||||||
|
#include "util/RatelimitBucket.hpp"
|
||||||
|
|
||||||
|
#include <IrcMessage>
|
||||||
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -18,11 +24,30 @@ class TwitchChannel;
|
||||||
class BttvEmotes;
|
class BttvEmotes;
|
||||||
class FfzEmotes;
|
class FfzEmotes;
|
||||||
class SeventvEmotes;
|
class SeventvEmotes;
|
||||||
|
class RatelimitBucket;
|
||||||
|
|
||||||
class ITwitchIrcServer
|
class ITwitchIrcServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
ITwitchIrcServer() = default;
|
||||||
virtual ~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<void(ChannelPtr)> func) = 0;
|
||||||
|
|
||||||
virtual void forEachChannelAndSpecialChannels(
|
virtual void forEachChannelAndSpecialChannels(
|
||||||
std::function<void(ChannelPtr)> func) = 0;
|
std::function<void(ChannelPtr)> func) = 0;
|
||||||
|
@ -46,9 +71,14 @@ public:
|
||||||
// Update this interface with TwitchIrcServer methods as needed
|
// Update this interface with TwitchIrcServer methods as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
class TwitchIrcServer final : public AbstractIrcServer, public ITwitchIrcServer
|
class TwitchIrcServer final : public ITwitchIrcServer, public QObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum class ConnectionType {
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
};
|
||||||
|
|
||||||
TwitchIrcServer();
|
TwitchIrcServer();
|
||||||
~TwitchIrcServer() override = default;
|
~TwitchIrcServer() override = default;
|
||||||
|
|
||||||
|
@ -88,6 +118,25 @@ public:
|
||||||
void dropSeventvChannel(const QString &userID,
|
void dropSeventvChannel(const QString &userID,
|
||||||
const QString &emoteSetID) override;
|
const QString &emoteSetID) override;
|
||||||
|
|
||||||
|
void addFakeMessage(const QString &data) override;
|
||||||
|
|
||||||
|
void addGlobalSystemMessage(const QString &messageText) override;
|
||||||
|
|
||||||
|
// iteration
|
||||||
|
void forEachChannel(std::function<void(ChannelPtr)> 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:
|
private:
|
||||||
Atomic<QString> lastUserThatWhisperedMe;
|
Atomic<QString> lastUserThatWhisperedMe;
|
||||||
|
|
||||||
|
@ -109,19 +158,21 @@ public:
|
||||||
void setLastUserThatWhisperedMe(const QString &user) override;
|
void setLastUserThatWhisperedMe(const QString &user) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void initializeConnection(IrcConnection *connection,
|
void initializeConnection(IrcConnection *connection, ConnectionType type);
|
||||||
ConnectionType type) override;
|
std::shared_ptr<Channel> createChannel(const QString &channelName);
|
||||||
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
|
|
||||||
|
|
||||||
void privateMessageReceived(Communi::IrcPrivateMessage *message) override;
|
void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||||
void readConnectionMessageReceived(Communi::IrcMessage *message) override;
|
void readConnectionMessageReceived(Communi::IrcMessage *message);
|
||||||
void writeConnectionMessageReceived(Communi::IrcMessage *message) override;
|
void writeConnectionMessageReceived(Communi::IrcMessage *message);
|
||||||
|
|
||||||
std::shared_ptr<Channel> getCustomChannel(
|
void onReadConnected(IrcConnection *connection);
|
||||||
const QString &channelname) override;
|
void onWriteConnected(IrcConnection *connection);
|
||||||
|
void onDisconnected();
|
||||||
|
void markChannelsConnected();
|
||||||
|
|
||||||
QString cleanChannelName(const QString &dirtyChannelName) override;
|
std::shared_ptr<Channel> getCustomChannel(const QString &channelname);
|
||||||
bool hasSeparateWriteConnection() const override;
|
|
||||||
|
QString cleanChannelName(const QString &dirtyChannelName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onMessageSendRequested(const std::shared_ptr<TwitchChannel> &channel,
|
void onMessageSendRequested(const std::shared_ptr<TwitchChannel> &channel,
|
||||||
|
@ -132,6 +183,23 @@ private:
|
||||||
|
|
||||||
bool prepareToSend(const std::shared_ptr<TwitchChannel> &channel);
|
bool prepareToSend(const std::shared_ptr<TwitchChannel> &channel);
|
||||||
|
|
||||||
|
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||||
|
std::mutex channelMutex;
|
||||||
|
|
||||||
|
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
||||||
|
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
||||||
|
|
||||||
|
// Our rate limiting bucket for the Twitch join rate limits
|
||||||
|
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
||||||
|
QObjectPtr<RatelimitBucket> joinBucket_;
|
||||||
|
|
||||||
|
QTimer reconnectTimer_;
|
||||||
|
int falloffCounter_ = 1;
|
||||||
|
|
||||||
|
std::mutex connectionMutex_;
|
||||||
|
|
||||||
|
pajlada::Signals::SignalHolder connections_;
|
||||||
|
|
||||||
std::mutex lastMessageMutex_;
|
std::mutex lastMessageMutex_;
|
||||||
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
|
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
|
||||||
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
||||||
|
|
|
@ -511,7 +511,6 @@ public:
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
||||||
#endif
|
#endif
|
||||||
BoolSetting enableExperimentalIrc = {"/misc/experimentalIrc", false};
|
|
||||||
|
|
||||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||||
|
@ -548,14 +547,7 @@ public:
|
||||||
true};
|
true};
|
||||||
BoolSetting lockNotebookLayout = {"/misc/lockNotebookLayout", false};
|
BoolSetting lockNotebookLayout = {"/misc/lockNotebookLayout", false};
|
||||||
|
|
||||||
/// Debug
|
|
||||||
BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages",
|
|
||||||
false};
|
|
||||||
|
|
||||||
/// UI
|
/// 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};
|
BoolSetting showSendButton = {"/ui/showSendButton", false};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "messages/MessageElement.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 "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -643,19 +640,6 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
|
||||||
obj.insert("type", "live");
|
obj.insert("type", "live");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Channel::Type::Irc: {
|
|
||||||
if (auto *ircChannel =
|
|
||||||
dynamic_cast<IrcChannel *>(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: {
|
case Channel::Type::Misc: {
|
||||||
obj.insert("type", "misc");
|
obj.insert("type", "misc");
|
||||||
obj.insert("name", channel.get()->getName());
|
obj.insert("name", channel.get()->getName());
|
||||||
|
@ -705,11 +689,6 @@ IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor)
|
||||||
{
|
{
|
||||||
return getApp()->getTwitch()->getAutomodChannel();
|
return getApp()->getTwitch()->getAutomodChannel();
|
||||||
}
|
}
|
||||||
else if (descriptor.type_ == "irc")
|
|
||||||
{
|
|
||||||
return Irc::instance().getOrAddChannel(descriptor.server_,
|
|
||||||
descriptor.channelName_);
|
|
||||||
}
|
|
||||||
else if (descriptor.type_ == "misc")
|
else if (descriptor.type_ == "misc")
|
||||||
{
|
{
|
||||||
return getApp()->getTwitchAbstract()->getChannelOrEmpty(
|
return getApp()->getTwitchAbstract()->getChannelOrEmpty(
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
|
|
|
@ -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<int>(&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
|
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "providers/irc/Irc2.hpp"
|
|
||||||
#include "widgets/BaseWindow.hpp"
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,261 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>IrcConnectionEditor</class>
|
|
||||||
<widget class="QDialog" name="IrcConnectionEditor">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>329</width>
|
|
||||||
<height>414</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Dialog</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="serverLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Host:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="serverLineEdit">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>irc.example.com</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="portLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Port:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QSpinBox" name="portSpinBox">
|
|
||||||
<property name="maximum">
|
|
||||||
<number>65636</number>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>6697</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="securityLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>SSL:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QCheckBox" name="securityCheckBox">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="userNameLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>User Name:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QLineEdit" name="userNameLineEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QLabel" name="nickNameLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Nick Name:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="QLineEdit" name="nickNameLineEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0">
|
|
||||||
<widget class="QLabel" name="realNameLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Real Name:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="1">
|
|
||||||
<widget class="QLineEdit" name="realNameLineEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="0">
|
|
||||||
<widget class="QLabel" name="loginMethodLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Login method:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="9" column="0">
|
|
||||||
<widget class="QLabel" name="passwordLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Password:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="9" column="1">
|
|
||||||
<widget class="QLineEdit" name="passwordLineEdit">
|
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="0">
|
|
||||||
<spacer name="verticalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="1">
|
|
||||||
<widget class="QComboBox" name="loginMethodComboBox">
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Anonymous</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Custom</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Server Password (/PASS $password)</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>SASL</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="10" column="0">
|
|
||||||
<widget class="QLabel" name="connectCommandsLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Send IRC commands
|
|
||||||
on connect:</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::PlainText</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="10" column="1">
|
|
||||||
<widget class="QPlainTextEdit" name="connectCommandsEditor">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>1</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="11" column="1">
|
|
||||||
<spacer name="verticalSpacer_3">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<tabstops>
|
|
||||||
<tabstop>serverLineEdit</tabstop>
|
|
||||||
<tabstop>portSpinBox</tabstop>
|
|
||||||
<tabstop>securityCheckBox</tabstop>
|
|
||||||
<tabstop>userNameLineEdit</tabstop>
|
|
||||||
<tabstop>nickNameLineEdit</tabstop>
|
|
||||||
<tabstop>realNameLineEdit</tabstop>
|
|
||||||
<tabstop>loginMethodComboBox</tabstop>
|
|
||||||
<tabstop>passwordLineEdit</tabstop>
|
|
||||||
</tabstops>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>IrcConnectionEditor</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>254</x>
|
|
||||||
<y>248</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>IrcConnectionEditor</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>254</x>
|
|
||||||
<y>248</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
|
@ -3,14 +3,10 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "controllers/hotkeys/HotkeyController.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 "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "widgets/dialogs/IrcConnectionEditor.hpp"
|
|
||||||
#include "widgets/helper/EditableModelView.hpp"
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
#include "widgets/helper/NotebookTab.hpp"
|
#include "widgets/helper/NotebookTab.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
@ -28,7 +24,6 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
constexpr int TAB_TWITCH = 0;
|
constexpr int TAB_TWITCH = 0;
|
||||||
constexpr int TAB_IRC = 1;
|
|
||||||
|
|
||||||
SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
: BaseWindow(
|
: BaseWindow(
|
||||||
|
@ -175,76 +170,6 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
tab->setCustomTitle("Twitch");
|
tab->setCustomTitle("Twitch");
|
||||||
}
|
}
|
||||||
|
|
||||||
// irc
|
|
||||||
{
|
|
||||||
LayoutCreator<QWidget> obj(new QWidget());
|
|
||||||
auto outerBox = obj.setLayoutType<QFormLayout>();
|
|
||||||
|
|
||||||
{
|
|
||||||
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);
|
layout->setStretchFactor(notebook.getElement(), 1);
|
||||||
|
|
||||||
auto buttons =
|
auto buttons =
|
||||||
|
@ -265,29 +190,11 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
||||||
this->ui_.twitch.channel->setFocus();
|
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->addShortcuts();
|
||||||
|
|
||||||
this->ui_.irc.servers->getTableView()->selectRow(
|
|
||||||
getSettings()->lastSelectIrcConn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectChannelDialog::ok()
|
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
|
// accept and close
|
||||||
this->hasSelectedChannel_ = true;
|
this->hasSelectedChannel_ = true;
|
||||||
this->close();
|
this->close();
|
||||||
|
@ -334,31 +241,6 @@ void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel)
|
||||||
this->ui_.twitch.automod->setFocus();
|
this->ui_.twitch.automod->setFocus();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Channel::Type::Irc: {
|
|
||||||
this->ui_.notebook->selectIndex(TAB_IRC);
|
|
||||||
this->ui_.irc.channel->setText(_channel.get()->getName());
|
|
||||||
|
|
||||||
if (auto *ircChannel =
|
|
||||||
dynamic_cast<IrcChannel *>(_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: {
|
default: {
|
||||||
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
||||||
this->ui_.twitch.channel->setFocus();
|
this->ui_.twitch.channel->setFocus();
|
||||||
|
@ -405,25 +287,6 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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_;
|
return this->selectedChannel_;
|
||||||
|
@ -559,60 +422,9 @@ void SelectChannelDialog::addShortcuts()
|
||||||
{"scrollPage", nullptr},
|
{"scrollPage", nullptr},
|
||||||
{"search", nullptr},
|
{"search", nullptr},
|
||||||
{"delete", nullptr},
|
{"delete", nullptr},
|
||||||
|
{"openTab", nullptr},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (getSettings()->enableExperimentalIrc)
|
|
||||||
{
|
|
||||||
actions.insert(
|
|
||||||
{"openTab", [this](std::vector<QString> 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(
|
this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory(
|
||||||
HotkeyCategory::PopupWindow, actions, this);
|
HotkeyCategory::PopupWindow, actions, this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,6 @@ private:
|
||||||
QRadioButton *live;
|
QRadioButton *live;
|
||||||
QRadioButton *automod;
|
QRadioButton *automod;
|
||||||
} twitch;
|
} twitch;
|
||||||
struct {
|
|
||||||
QLineEdit *channel;
|
|
||||||
EditableModelView *servers;
|
|
||||||
} irc;
|
|
||||||
} ui_;
|
} ui_;
|
||||||
|
|
||||||
EventFilter tabFilter_;
|
EventFilter tabFilter_;
|
||||||
|
|
|
@ -752,8 +752,7 @@ void UserInfoPopup::setData(const QString &name,
|
||||||
|
|
||||||
auto type = this->channel_->getType();
|
auto type = this->channel_->getType();
|
||||||
if (type == Channel::Type::TwitchLive ||
|
if (type == Channel::Type::TwitchLive ||
|
||||||
type == Channel::Type::TwitchWhispers || type == Channel::Type::Irc ||
|
type == Channel::Type::TwitchWhispers || type == Channel::Type::Misc)
|
||||||
type == Channel::Type::Misc)
|
|
||||||
{
|
{
|
||||||
// not a normal twitch channel, the url opened by the button will be invalid, so hide the button
|
// not a normal twitch channel, the url opened by the button will be invalid, so hide the button
|
||||||
this->ui_.usercardLabel->hide();
|
this->ui_.usercardLabel->hide();
|
||||||
|
|
|
@ -2782,7 +2782,6 @@ bool ChannelView::mayContainMessage(const MessagePtr &message)
|
||||||
case Channel::Type::Direct:
|
case Channel::Type::Direct:
|
||||||
case Channel::Type::Twitch:
|
case Channel::Type::Twitch:
|
||||||
case Channel::Type::TwitchWatching:
|
case Channel::Type::TwitchWatching:
|
||||||
case Channel::Type::Irc:
|
|
||||||
// XXX: system messages may not have the channel set
|
// XXX: system messages may not have the channel set
|
||||||
return message->flags.has(MessageFlag::System) ||
|
return message->flags.has(MessageFlag::System) ||
|
||||||
this->channel()->getName() == message->channelName;
|
this->channel()->getName() == message->channelName;
|
||||||
|
|
|
@ -1129,13 +1129,6 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
||||||
layout.addIntInput("Usercard scrollback limit (requires restart)",
|
layout.addIntInput("Usercard scrollback limit (requires restart)",
|
||||||
s.scrollbackUsercardLimit, 100, 100000, 100);
|
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<int>(
|
layout.addDropdown<int>(
|
||||||
"Stack timeouts", {"Stack", "Stack until timeout", "Don't stack"},
|
"Stack timeouts", {"Stack", "Stack until timeout", "Don't stack"},
|
||||||
s.timeoutStackStyle,
|
s.timeoutStackStyle,
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/WindowDescriptors.hpp"
|
#include "common/WindowDescriptors.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "providers/irc/IrcChannel2.hpp"
|
|
||||||
#include "providers/irc/IrcServer.hpp"
|
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
@ -817,22 +815,6 @@ NodeDescriptor SplitContainer::buildDescriptorRecursively(
|
||||||
|
|
||||||
SplitNodeDescriptor result;
|
SplitNodeDescriptor result;
|
||||||
result.type_ = qmagicenum::enumNameString(channelType);
|
result.type_ = qmagicenum::enumNameString(channelType);
|
||||||
|
|
||||||
switch (channelType)
|
|
||||||
{
|
|
||||||
case Channel::Type::Irc: {
|
|
||||||
if (auto *ircChannel = dynamic_cast<IrcChannel *>(
|
|
||||||
currentNode->split_->getChannel().get()))
|
|
||||||
{
|
|
||||||
if (ircChannel->server())
|
|
||||||
{
|
|
||||||
result.server_ = ircChannel->server()->id();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.channelName_ = currentNode->split_->getChannel()->getName();
|
result.channelName_ = currentNode->split_->getChannel()->getName();
|
||||||
result.filters_ = currentNode->split_->getFilters();
|
result.filters_ = currentNode->split_->getFilters();
|
||||||
return result;
|
return result;
|
||||||
|
|
Loading…
Reference in a new issue