mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
commit
9b6f53ab75
64 changed files with 2130 additions and 305 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -22,6 +22,9 @@
|
||||||
[submodule "lib/rapidjson"]
|
[submodule "lib/rapidjson"]
|
||||||
path = lib/rapidjson
|
path = lib/rapidjson
|
||||||
url = https://github.com/Tencent/rapidjson
|
url = https://github.com/Tencent/rapidjson
|
||||||
|
[submodule "lib/qtkeychain"]
|
||||||
|
path = lib/qtkeychain
|
||||||
|
url = https://github.com/Chatterino/qtkeychain
|
||||||
[submodule "lib/websocketpp"]
|
[submodule "lib/websocketpp"]
|
||||||
path = lib/websocketpp
|
path = lib/websocketpp
|
||||||
url = https://github.com/ziocleto/websocketpp
|
url = https://github.com/ziocleto/websocketpp
|
||||||
|
|
|
@ -40,8 +40,6 @@ macx {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Submodules
|
# Submodules
|
||||||
DEFINES += IRC_NAMESPACE=Communi
|
|
||||||
|
|
||||||
include(lib/warnings.pri)
|
include(lib/warnings.pri)
|
||||||
include(lib/appbase.pri)
|
include(lib/appbase.pri)
|
||||||
include(lib/fmt.pri)
|
include(lib/fmt.pri)
|
||||||
|
@ -54,6 +52,7 @@ include(lib/settings.pri)
|
||||||
include(lib/serialize.pri)
|
include(lib/serialize.pri)
|
||||||
include(lib/winsdk.pri)
|
include(lib/winsdk.pri)
|
||||||
include(lib/rapidjson.pri)
|
include(lib/rapidjson.pri)
|
||||||
|
include(lib/qtkeychain.pri)
|
||||||
|
|
||||||
exists( $$OUT_PWD/conanbuildinfo.pri ) {
|
exists( $$OUT_PWD/conanbuildinfo.pri ) {
|
||||||
message("Using conan packages")
|
message("Using conan packages")
|
||||||
|
@ -78,7 +77,9 @@ SOURCES += \
|
||||||
src/autogenerated/ResourcesAutogen.cpp \
|
src/autogenerated/ResourcesAutogen.cpp \
|
||||||
src/BrowserExtension.cpp \
|
src/BrowserExtension.cpp \
|
||||||
src/common/Channel.cpp \
|
src/common/Channel.cpp \
|
||||||
|
src/common/ChannelChatters.cpp \
|
||||||
src/common/CompletionModel.cpp \
|
src/common/CompletionModel.cpp \
|
||||||
|
src/common/Credentials.cpp \
|
||||||
src/common/DownloadManager.cpp \
|
src/common/DownloadManager.cpp \
|
||||||
src/common/Env.cpp \
|
src/common/Env.cpp \
|
||||||
src/common/LinkParser.cpp \
|
src/common/LinkParser.cpp \
|
||||||
|
@ -132,8 +133,10 @@ SOURCES += \
|
||||||
src/providers/emoji/Emojis.cpp \
|
src/providers/emoji/Emojis.cpp \
|
||||||
src/providers/ffz/FfzEmotes.cpp \
|
src/providers/ffz/FfzEmotes.cpp \
|
||||||
src/providers/irc/AbstractIrcServer.cpp \
|
src/providers/irc/AbstractIrcServer.cpp \
|
||||||
|
src/providers/irc/Irc2.cpp \
|
||||||
src/providers/irc/IrcAccount.cpp \
|
src/providers/irc/IrcAccount.cpp \
|
||||||
src/providers/irc/IrcChannel2.cpp \
|
src/providers/irc/IrcChannel2.cpp \
|
||||||
|
src/providers/irc/IrcCommands.cpp \
|
||||||
src/providers/irc/IrcConnection2.cpp \
|
src/providers/irc/IrcConnection2.cpp \
|
||||||
src/providers/irc/IrcServer.cpp \
|
src/providers/irc/IrcServer.cpp \
|
||||||
src/providers/LinkResolver.cpp \
|
src/providers/LinkResolver.cpp \
|
||||||
|
@ -150,9 +153,9 @@ SOURCES += \
|
||||||
src/providers/twitch/TwitchChannel.cpp \
|
src/providers/twitch/TwitchChannel.cpp \
|
||||||
src/providers/twitch/TwitchEmotes.cpp \
|
src/providers/twitch/TwitchEmotes.cpp \
|
||||||
src/providers/twitch/TwitchHelpers.cpp \
|
src/providers/twitch/TwitchHelpers.cpp \
|
||||||
|
src/providers/twitch/TwitchIrcServer.cpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.cpp \
|
src/providers/twitch/TwitchMessageBuilder.cpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
||||||
src/providers/twitch/TwitchServer.cpp \
|
|
||||||
src/providers/twitch/TwitchUser.cpp \
|
src/providers/twitch/TwitchUser.cpp \
|
||||||
src/RunGui.cpp \
|
src/RunGui.cpp \
|
||||||
src/singletons/Badges.cpp \
|
src/singletons/Badges.cpp \
|
||||||
|
@ -180,6 +183,7 @@ SOURCES += \
|
||||||
src/widgets/AccountSwitchWidget.cpp \
|
src/widgets/AccountSwitchWidget.cpp \
|
||||||
src/widgets/AttachedWindow.cpp \
|
src/widgets/AttachedWindow.cpp \
|
||||||
src/widgets/dialogs/EmotePopup.cpp \
|
src/widgets/dialogs/EmotePopup.cpp \
|
||||||
|
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||||
src/widgets/dialogs/LoginDialog.cpp \
|
src/widgets/dialogs/LoginDialog.cpp \
|
||||||
src/widgets/dialogs/LogsPopup.cpp \
|
src/widgets/dialogs/LogsPopup.cpp \
|
||||||
|
@ -230,9 +234,11 @@ HEADERS += \
|
||||||
src/common/Aliases.hpp \
|
src/common/Aliases.hpp \
|
||||||
src/common/Atomic.hpp \
|
src/common/Atomic.hpp \
|
||||||
src/common/Channel.hpp \
|
src/common/Channel.hpp \
|
||||||
|
src/common/ChannelChatters.hpp \
|
||||||
src/common/Common.hpp \
|
src/common/Common.hpp \
|
||||||
src/common/CompletionModel.hpp \
|
src/common/CompletionModel.hpp \
|
||||||
src/common/ConcurrentMap.hpp \
|
src/common/ConcurrentMap.hpp \
|
||||||
|
src/common/Credentials.hpp \
|
||||||
src/common/DownloadManager.hpp \
|
src/common/DownloadManager.hpp \
|
||||||
src/common/Env.hpp \
|
src/common/Env.hpp \
|
||||||
src/common/LinkParser.hpp \
|
src/common/LinkParser.hpp \
|
||||||
|
@ -302,8 +308,10 @@ HEADERS += \
|
||||||
src/providers/emoji/Emojis.hpp \
|
src/providers/emoji/Emojis.hpp \
|
||||||
src/providers/ffz/FfzEmotes.hpp \
|
src/providers/ffz/FfzEmotes.hpp \
|
||||||
src/providers/irc/AbstractIrcServer.hpp \
|
src/providers/irc/AbstractIrcServer.hpp \
|
||||||
|
src/providers/irc/Irc2.hpp \
|
||||||
src/providers/irc/IrcAccount.hpp \
|
src/providers/irc/IrcAccount.hpp \
|
||||||
src/providers/irc/IrcChannel2.hpp \
|
src/providers/irc/IrcChannel2.hpp \
|
||||||
|
src/providers/irc/IrcCommands.hpp \
|
||||||
src/providers/irc/IrcConnection2.hpp \
|
src/providers/irc/IrcConnection2.hpp \
|
||||||
src/providers/irc/IrcServer.hpp \
|
src/providers/irc/IrcServer.hpp \
|
||||||
src/providers/LinkResolver.hpp \
|
src/providers/LinkResolver.hpp \
|
||||||
|
@ -322,9 +330,9 @@ HEADERS += \
|
||||||
src/providers/twitch/TwitchCommon.hpp \
|
src/providers/twitch/TwitchCommon.hpp \
|
||||||
src/providers/twitch/TwitchEmotes.hpp \
|
src/providers/twitch/TwitchEmotes.hpp \
|
||||||
src/providers/twitch/TwitchHelpers.hpp \
|
src/providers/twitch/TwitchHelpers.hpp \
|
||||||
|
src/providers/twitch/TwitchIrcServer.hpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.hpp \
|
src/providers/twitch/TwitchMessageBuilder.hpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
||||||
src/providers/twitch/TwitchServer.hpp \
|
|
||||||
src/providers/twitch/TwitchUser.hpp \
|
src/providers/twitch/TwitchUser.hpp \
|
||||||
src/RunGui.hpp \
|
src/RunGui.hpp \
|
||||||
src/singletons/Badges.hpp \
|
src/singletons/Badges.hpp \
|
||||||
|
@ -350,6 +358,7 @@ HEADERS += \
|
||||||
src/util/IsBigEndian.hpp \
|
src/util/IsBigEndian.hpp \
|
||||||
src/util/JsonQuery.hpp \
|
src/util/JsonQuery.hpp \
|
||||||
src/util/LayoutCreator.hpp \
|
src/util/LayoutCreator.hpp \
|
||||||
|
src/util/Overloaded.hpp \
|
||||||
src/util/QObjectRef.hpp \
|
src/util/QObjectRef.hpp \
|
||||||
src/util/QStringHash.hpp \
|
src/util/QStringHash.hpp \
|
||||||
src/util/rangealgorithm.hpp \
|
src/util/rangealgorithm.hpp \
|
||||||
|
@ -363,6 +372,7 @@ HEADERS += \
|
||||||
src/widgets/AccountSwitchWidget.hpp \
|
src/widgets/AccountSwitchWidget.hpp \
|
||||||
src/widgets/AttachedWindow.hpp \
|
src/widgets/AttachedWindow.hpp \
|
||||||
src/widgets/dialogs/EmotePopup.hpp \
|
src/widgets/dialogs/EmotePopup.hpp \
|
||||||
|
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||||
src/widgets/dialogs/LoginDialog.hpp \
|
src/widgets/dialogs/LoginDialog.hpp \
|
||||||
src/widgets/dialogs/LogsPopup.hpp \
|
src/widgets/dialogs/LogsPopup.hpp \
|
||||||
|
@ -414,7 +424,8 @@ RESOURCES += \
|
||||||
|
|
||||||
DISTFILES +=
|
DISTFILES +=
|
||||||
|
|
||||||
FORMS +=
|
FORMS += \
|
||||||
|
src/widgets/dialogs/IrcConnectionEditor.ui
|
||||||
|
|
||||||
# do not use windows min/max macros
|
# do not use windows min/max macros
|
||||||
#win32 {
|
#win32 {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
DEFINES += IRC_NAMESPACE=Communi
|
||||||
|
|
||||||
include(../lib/libcommuni/src/core/core.pri)
|
include(../lib/libcommuni/src/core/core.pri)
|
||||||
include(../lib/libcommuni/src/model/model.pri)
|
include(../lib/libcommuni/src/model/model.pri)
|
||||||
include(../lib/libcommuni/src/util/util.pri)
|
include(../lib/libcommuni/src/util/util.pri)
|
||||||
|
|
1
lib/qtkeychain
Submodule
1
lib/qtkeychain
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 832f550da3f6655168a737d2e1b7df37272e936d
|
1
lib/qtkeychain.pri
Normal file
1
lib/qtkeychain.pri
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include(qtkeychain/qt5keychain.pri)
|
0
resources/licenses/qtkeychain.txt
Normal file
0
resources/licenses/qtkeychain.txt
Normal file
|
@ -46,6 +46,7 @@
|
||||||
<file>licenses/pajlada_settings.txt</file>
|
<file>licenses/pajlada_settings.txt</file>
|
||||||
<file>licenses/pajlada_signals.txt</file>
|
<file>licenses/pajlada_signals.txt</file>
|
||||||
<file>licenses/qt_lgpl-3.0.txt</file>
|
<file>licenses/qt_lgpl-3.0.txt</file>
|
||||||
|
<file>licenses/qtkeychain.txt</file>
|
||||||
<file>licenses/rapidjson.txt</file>
|
<file>licenses/rapidjson.txt</file>
|
||||||
<file>licenses/websocketpp.txt</file>
|
<file>licenses/websocketpp.txt</file>
|
||||||
<file>pajaDank.png</file>
|
<file>pajaDank.png</file>
|
||||||
|
|
34
src/.clang-format
Normal file
34
src/.clang-format
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
Language: Cpp
|
||||||
|
|
||||||
|
AccessModifierOffset: -1
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignEscapedNewlinesLeft: true
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
BasedOnStyle: Google
|
||||||
|
BraceWrapping: {
|
||||||
|
AfterNamespace: 'false'
|
||||||
|
AfterClass: 'true'
|
||||||
|
BeforeElse: 'true'
|
||||||
|
AfterControlStatement: 'true'
|
||||||
|
AfterFunction: 'true'
|
||||||
|
BeforeCatch: 'true'
|
||||||
|
}
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
DerivePointerBinding: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: true
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
NamespaceIndentation: Inner
|
||||||
|
PointerBindsToType: false
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
Standard: Auto
|
||||||
|
ReflowComments: false
|
|
@ -13,8 +13,9 @@
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
|
#include "providers/irc/Irc2.hpp"
|
||||||
#include "providers/twitch/PubsubClient.hpp"
|
#include "providers/twitch/PubsubClient.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/Logging.hpp"
|
#include "singletons/Logging.hpp"
|
||||||
|
@ -59,7 +60,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
, ignores(&this->emplace<IgnoreController>())
|
, ignores(&this->emplace<IgnoreController>())
|
||||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
, taggedUsers(&this->emplace<TaggedUsersController>())
|
||||||
, moderationActions(&this->emplace<ModerationActions>())
|
, moderationActions(&this->emplace<ModerationActions>())
|
||||||
, twitch2(&this->emplace<TwitchServer>())
|
, twitch2(&this->emplace<TwitchIrcServer>())
|
||||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||||
, logging(&this->emplace<Logging>())
|
, logging(&this->emplace<Logging>())
|
||||||
|
|
||||||
|
@ -78,6 +79,8 @@ void Application::initialize(Settings &settings, Paths &paths)
|
||||||
assert(isAppInitialized == false);
|
assert(isAppInitialized == false);
|
||||||
isAppInitialized = true;
|
isAppInitialized = true;
|
||||||
|
|
||||||
|
Irc::getInstance().load();
|
||||||
|
|
||||||
for (auto &singleton : this->singletons_)
|
for (auto &singleton : this->singletons_)
|
||||||
{
|
{
|
||||||
singleton->initialize(settings, paths);
|
singleton->initialize(settings, paths);
|
||||||
|
@ -116,6 +119,8 @@ void Application::save()
|
||||||
|
|
||||||
void Application::initNm(Paths &paths)
|
void Application::initNm(Paths &paths)
|
||||||
{
|
{
|
||||||
|
(void)paths;
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
# if defined QT_NO_DEBUG || defined C_DEBUG_NM
|
# if defined QT_NO_DEBUG || defined C_DEBUG_NM
|
||||||
registerNmHost(paths);
|
registerNmHost(paths);
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchServer;
|
class TwitchIrcServer;
|
||||||
class PubSub;
|
class PubSub;
|
||||||
|
|
||||||
class CommandController;
|
class CommandController;
|
||||||
|
@ -67,14 +67,14 @@ public:
|
||||||
IgnoreController *const ignores{};
|
IgnoreController *const ignores{};
|
||||||
TaggedUsersController *const taggedUsers{};
|
TaggedUsersController *const taggedUsers{};
|
||||||
ModerationActions *const moderationActions{};
|
ModerationActions *const moderationActions{};
|
||||||
TwitchServer *const twitch2{};
|
TwitchIrcServer *const twitch2{};
|
||||||
ChatterinoBadges *const chatterinoBadges{};
|
ChatterinoBadges *const chatterinoBadges{};
|
||||||
|
|
||||||
/*[[deprecated]]*/ Logging *const logging{};
|
/*[[deprecated]]*/ Logging *const logging{};
|
||||||
|
|
||||||
/// Provider-specific
|
/// Provider-specific
|
||||||
struct {
|
struct {
|
||||||
/*[[deprecated("use twitch2 instead")]]*/ TwitchServer *server{};
|
/*[[deprecated("use twitch2 instead")]]*/ TwitchIrcServer *server{};
|
||||||
/*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{};
|
/*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{};
|
||||||
} twitch;
|
} twitch;
|
||||||
|
|
||||||
|
|
|
@ -70,13 +70,6 @@ void Channel::addMessage(MessagePtr message,
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
MessagePtr deleted;
|
MessagePtr deleted;
|
||||||
|
|
||||||
const QString &username = message->loginName;
|
|
||||||
if (!username.isEmpty())
|
|
||||||
{
|
|
||||||
// TODO: Add recent chatters display name
|
|
||||||
this->addRecentChatter(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FOURTF: change this when adding more providers
|
// FOURTF: change this when adding more providers
|
||||||
if (this->isTwitchChannel() &&
|
if (this->isTwitchChannel() &&
|
||||||
(!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
|
(!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
|
||||||
|
@ -246,10 +239,6 @@ void Channel::deleteMessage(QString messageID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Channel::addRecentChatter(const MessagePtr &message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Channel::canSendMessage() const
|
bool Channel::canSendMessage() const
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -291,6 +280,15 @@ bool Channel::shouldIgnoreHighlights() const
|
||||||
this->type_ == Type::TwitchWhispers;
|
this->type_ == Type::TwitchWhispers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Channel::canReconnect() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::reconnect()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> Channel::getEmpty()
|
std::shared_ptr<Channel> Channel::getEmpty()
|
||||||
{
|
{
|
||||||
static std::shared_ptr<Channel> channel(new Channel("", Type::None));
|
static std::shared_ptr<Channel> channel(new Channel("", Type::None));
|
||||||
|
|
|
@ -37,15 +37,16 @@ public:
|
||||||
TwitchWatching,
|
TwitchWatching,
|
||||||
TwitchMentions,
|
TwitchMentions,
|
||||||
TwitchEnd,
|
TwitchEnd,
|
||||||
|
Irc,
|
||||||
Misc
|
Misc
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Channel(const QString &name, Type type);
|
explicit Channel(const QString &name, Type type);
|
||||||
virtual ~Channel();
|
virtual ~Channel();
|
||||||
|
|
||||||
|
// SIGNALS
|
||||||
pajlada::Signals::Signal<const QString &, const QString &, bool &>
|
pajlada::Signals::Signal<const QString &, const QString &, bool &>
|
||||||
sendMessageSignal;
|
sendMessageSignal;
|
||||||
|
|
||||||
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
|
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
|
||||||
pajlada::Signals::Signal<MessagePtr &, boost::optional<MessageFlags>>
|
pajlada::Signals::Signal<MessagePtr &, boost::optional<MessageFlags>>
|
||||||
messageAppended;
|
messageAppended;
|
||||||
|
@ -60,6 +61,7 @@ public:
|
||||||
virtual bool isEmpty() const;
|
virtual bool isEmpty() const;
|
||||||
LimitedQueueSnapshot<MessagePtr> getMessageSnapshot();
|
LimitedQueueSnapshot<MessagePtr> getMessageSnapshot();
|
||||||
|
|
||||||
|
// MESSAGES
|
||||||
// overridingFlags can be filled in with flags that should be used instead
|
// overridingFlags can be filled in with flags that should be used instead
|
||||||
// of the message's flags. This is useful in case a flag is specific to a
|
// of the message's flags. This is useful in case a flag is specific to a
|
||||||
// type of split
|
// type of split
|
||||||
|
@ -71,9 +73,11 @@ public:
|
||||||
void disableAllMessages();
|
void disableAllMessages();
|
||||||
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
||||||
void deleteMessage(QString messageID);
|
void deleteMessage(QString messageID);
|
||||||
|
void clearMessages();
|
||||||
|
|
||||||
QStringList modList;
|
QStringList modList;
|
||||||
|
|
||||||
|
// CHANNEL INFO
|
||||||
virtual bool canSendMessage() const;
|
virtual bool canSendMessage() const;
|
||||||
virtual void sendMessage(const QString &message);
|
virtual void sendMessage(const QString &message);
|
||||||
virtual bool isMod() const;
|
virtual bool isMod() const;
|
||||||
|
@ -82,6 +86,8 @@ public:
|
||||||
virtual bool hasHighRateLimit() const;
|
virtual bool hasHighRateLimit() const;
|
||||||
virtual bool isLive() const;
|
virtual bool isLive() const;
|
||||||
virtual bool shouldIgnoreHighlights() const;
|
virtual bool shouldIgnoreHighlights() const;
|
||||||
|
virtual bool canReconnect() const;
|
||||||
|
virtual void reconnect();
|
||||||
|
|
||||||
static std::shared_ptr<Channel> getEmpty();
|
static std::shared_ptr<Channel> getEmpty();
|
||||||
|
|
||||||
|
@ -89,7 +95,6 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void onConnected();
|
virtual void onConnected();
|
||||||
virtual void addRecentChatter(const MessagePtr &message);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString name_;
|
const QString name_;
|
||||||
|
|
72
src/common/ChannelChatters.cpp
Normal file
72
src/common/ChannelChatters.cpp
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
#include "ChannelChatters.hpp"
|
||||||
|
|
||||||
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
ChannelChatters::ChannelChatters(Channel &channel)
|
||||||
|
: channel_(channel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessGuard<const UsernameSet> ChannelChatters::accessChatters() const
|
||||||
|
{
|
||||||
|
return this->chatters_.accessConst();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelChatters::addRecentChatter(const QString &user)
|
||||||
|
{
|
||||||
|
this->chatters_.access()->insert(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelChatters::addJoinedUser(const QString &user)
|
||||||
|
{
|
||||||
|
auto joinedUsers = this->joinedUsers_.access();
|
||||||
|
joinedUsers->append(user);
|
||||||
|
|
||||||
|
if (!this->joinedUsersMergeQueued_)
|
||||||
|
{
|
||||||
|
this->joinedUsersMergeQueued_ = true;
|
||||||
|
|
||||||
|
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||||
|
auto joinedUsers = this->joinedUsers_.access();
|
||||||
|
|
||||||
|
MessageBuilder builder(systemMessage,
|
||||||
|
"Users joined: " + joinedUsers->join(", "));
|
||||||
|
builder->flags.set(MessageFlag::Collapsed);
|
||||||
|
joinedUsers->clear();
|
||||||
|
this->channel_.addMessage(builder.release());
|
||||||
|
this->joinedUsersMergeQueued_ = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelChatters::addPartedUser(const QString &user)
|
||||||
|
{
|
||||||
|
auto partedUsers = this->partedUsers_.access();
|
||||||
|
partedUsers->append(user);
|
||||||
|
|
||||||
|
if (!this->partedUsersMergeQueued_)
|
||||||
|
{
|
||||||
|
this->partedUsersMergeQueued_ = true;
|
||||||
|
|
||||||
|
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||||
|
auto partedUsers = this->partedUsers_.access();
|
||||||
|
|
||||||
|
MessageBuilder builder(systemMessage,
|
||||||
|
"Users parted: " + partedUsers->join(", "));
|
||||||
|
builder->flags.set(MessageFlag::Collapsed);
|
||||||
|
this->channel_.addMessage(builder.release());
|
||||||
|
partedUsers->clear();
|
||||||
|
|
||||||
|
this->partedUsersMergeQueued_ = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ChannelChatters::setChatters(UsernameSet &&set)
|
||||||
|
{
|
||||||
|
*this->chatters_.access() = set;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
37
src/common/ChannelChatters.hpp
Normal file
37
src/common/ChannelChatters.hpp
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/UniqueAccess.hpp"
|
||||||
|
#include "common/UsernameSet.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class ChannelChatters
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ChannelChatters(Channel &channel);
|
||||||
|
virtual ~ChannelChatters() = default; // add vtable
|
||||||
|
|
||||||
|
AccessGuard<const UsernameSet> accessChatters() const;
|
||||||
|
|
||||||
|
void addRecentChatter(const QString &user);
|
||||||
|
void addJoinedUser(const QString &user);
|
||||||
|
void addPartedUser(const QString &user);
|
||||||
|
void setChatters(UsernameSet &&set);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Channel &channel_;
|
||||||
|
|
||||||
|
// maps 2 char prefix to set of names
|
||||||
|
UniqueAccess<UsernameSet> chatters_;
|
||||||
|
|
||||||
|
// combines multiple joins/parts into one message
|
||||||
|
UniqueAccess<QStringList> joinedUsers_;
|
||||||
|
bool joinedUsersMergeQueued_ = false;
|
||||||
|
UniqueAccess<QStringList> partedUsers_;
|
||||||
|
bool partedUsersMergeQueued_ = false;
|
||||||
|
|
||||||
|
QObject lifetimeGuard_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -8,7 +8,7 @@
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
|
232
src/common/Credentials.cpp
Normal file
232
src/common/Credentials.cpp
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
#include "Credentials.hpp"
|
||||||
|
|
||||||
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
#include "keychain.h"
|
||||||
|
#include "singletons/Paths.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "util/CombinePath.hpp"
|
||||||
|
#include "util/Overloaded.hpp"
|
||||||
|
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#define FORMAT_NAME \
|
||||||
|
([&] { \
|
||||||
|
assert(!provider.contains(":")); \
|
||||||
|
return QString("chatterino:%1:%2").arg(provider).arg(name_); \
|
||||||
|
})()
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool useKeyring()
|
||||||
|
{
|
||||||
|
if (getPaths()->isPortable())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
return getSettings()->useKeyring;
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insecure storage:
|
||||||
|
QString insecurePath()
|
||||||
|
{
|
||||||
|
return combinePath(getPaths()->settingsDirectory, "credentials.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument loadInsecure()
|
||||||
|
{
|
||||||
|
QFile file(insecurePath());
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
return QJsonDocument::fromJson(file.readAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
void storeInsecure(const QJsonDocument &doc)
|
||||||
|
{
|
||||||
|
QSaveFile file(insecurePath());
|
||||||
|
file.open(QIODevice::WriteOnly);
|
||||||
|
file.write(doc.toJson());
|
||||||
|
file.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument &insecureInstance()
|
||||||
|
{
|
||||||
|
static auto store = loadInsecure();
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
void queueInsecureSave()
|
||||||
|
{
|
||||||
|
static bool isQueued = false;
|
||||||
|
|
||||||
|
if (!isQueued)
|
||||||
|
{
|
||||||
|
isQueued = true;
|
||||||
|
QTimer::singleShot(200, qApp, [] {
|
||||||
|
storeInsecure(insecureInstance());
|
||||||
|
isQueued = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QKeychain runs jobs asyncronously, so we have to assure that set/erase
|
||||||
|
// jobs gets executed in order.
|
||||||
|
struct SetJob {
|
||||||
|
QString name;
|
||||||
|
QString credential;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EraseJob {
|
||||||
|
QString name;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Job = std::variant<SetJob, EraseJob>;
|
||||||
|
|
||||||
|
static std::queue<Job> &jobQueue()
|
||||||
|
{
|
||||||
|
static std::queue<Job> jobs;
|
||||||
|
return jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runNextJob()
|
||||||
|
{
|
||||||
|
auto &&queue = jobQueue();
|
||||||
|
|
||||||
|
if (!queue.empty())
|
||||||
|
{
|
||||||
|
std::visit(
|
||||||
|
Overloaded{
|
||||||
|
[](const SetJob &set) {
|
||||||
|
qDebug() << "set";
|
||||||
|
auto job =
|
||||||
|
new QKeychain::WritePasswordJob("chatterino");
|
||||||
|
job->setAutoDelete(true);
|
||||||
|
job->setKey(set.name);
|
||||||
|
job->setTextData(set.credential);
|
||||||
|
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
||||||
|
[](auto) { runNextJob(); });
|
||||||
|
job->start();
|
||||||
|
},
|
||||||
|
[](const EraseJob &erase) {
|
||||||
|
qDebug() << "erase";
|
||||||
|
auto job =
|
||||||
|
new QKeychain::DeletePasswordJob("chatterino");
|
||||||
|
job->setAutoDelete(true);
|
||||||
|
job->setKey(erase.name);
|
||||||
|
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
||||||
|
[](auto) { runNextJob(); });
|
||||||
|
job->start();
|
||||||
|
}},
|
||||||
|
queue.front());
|
||||||
|
queue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queueJob(Job &&job)
|
||||||
|
{
|
||||||
|
auto &&queue = jobQueue();
|
||||||
|
|
||||||
|
queue.push(std::move(job));
|
||||||
|
if (queue.size() == 1)
|
||||||
|
{
|
||||||
|
runNextJob();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Credentials &Credentials::getInstance()
|
||||||
|
{
|
||||||
|
static Credentials creds;
|
||||||
|
return creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
Credentials::Credentials()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credentials::get(const QString &provider, const QString &name_,
|
||||||
|
QObject *receiver,
|
||||||
|
std::function<void(const QString &)> &&onLoaded)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
auto name = FORMAT_NAME;
|
||||||
|
|
||||||
|
if (useKeyring())
|
||||||
|
{
|
||||||
|
auto job = new QKeychain::ReadPasswordJob("chatterino");
|
||||||
|
job->setAutoDelete(true);
|
||||||
|
job->setKey(name);
|
||||||
|
QObject::connect(job, &QKeychain::Job::finished, receiver,
|
||||||
|
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
||||||
|
onLoaded(job->textData());
|
||||||
|
},
|
||||||
|
Qt::DirectConnection);
|
||||||
|
job->start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto &instance = insecureInstance();
|
||||||
|
|
||||||
|
onLoaded(instance.object().find(name).value().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credentials::set(const QString &provider, const QString &name_,
|
||||||
|
const QString &credential)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
/// On linux, we try to use a keychain but show a message to disable it when it fails.
|
||||||
|
/// XXX: add said message
|
||||||
|
|
||||||
|
auto name = FORMAT_NAME;
|
||||||
|
|
||||||
|
if (useKeyring())
|
||||||
|
{
|
||||||
|
qDebug() << "queue set";
|
||||||
|
queueJob(SetJob{name, credential});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto &instance = insecureInstance();
|
||||||
|
|
||||||
|
instance.object()[name] = credential;
|
||||||
|
|
||||||
|
queueInsecureSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credentials::erase(const QString &provider, const QString &name_)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
auto name = FORMAT_NAME;
|
||||||
|
|
||||||
|
if (useKeyring())
|
||||||
|
{
|
||||||
|
qDebug() << "queue erase";
|
||||||
|
queueJob(EraseJob{name});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto &instance = insecureInstance();
|
||||||
|
|
||||||
|
if (auto it = instance.object().find(name);
|
||||||
|
it != instance.object().end())
|
||||||
|
{
|
||||||
|
instance.object().erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
queueInsecureSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
23
src/common/Credentials.hpp
Normal file
23
src/common/Credentials.hpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class Credentials
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Credentials &getInstance();
|
||||||
|
|
||||||
|
void get(const QString &provider, const QString &name, QObject *receiver,
|
||||||
|
std::function<void(const QString &)> &&onLoaded);
|
||||||
|
void set(const QString &provider, const QString &name,
|
||||||
|
const QString &credential);
|
||||||
|
void erase(const QString &provider, const QString &name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Credentials();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -102,19 +102,26 @@ public:
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent) const override
|
int rowCount(const QModelIndex &parent) const override
|
||||||
{
|
{
|
||||||
|
(void)parent;
|
||||||
|
|
||||||
return this->rows_.size();
|
return this->rows_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int columnCount(const QModelIndex &parent) const override
|
int columnCount(const QModelIndex &parent) const override
|
||||||
{
|
{
|
||||||
|
(void)parent;
|
||||||
|
|
||||||
return this->columnCount_;
|
return this->columnCount_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role) const override
|
QVariant data(const QModelIndex &index, int role) const override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
||||||
column < this->columnCount_);
|
column >= this->columnCount_)
|
||||||
|
{
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
return rows_[row].items[column]->data(role);
|
return rows_[row].items[column]->data(role);
|
||||||
}
|
}
|
||||||
|
@ -123,8 +130,11 @@ public:
|
||||||
int role) override
|
int role) override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
||||||
column < this->columnCount_);
|
column >= this->columnCount_)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Row &rowItem = this->rows_[row];
|
Row &rowItem = this->rows_[row];
|
||||||
|
|
||||||
|
@ -185,6 +195,13 @@ public:
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
|
|
||||||
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
||||||
|
column >= this->columnCount_)
|
||||||
|
{
|
||||||
|
return Qt::NoItemFlags;
|
||||||
|
}
|
||||||
|
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
column < this->columnCount_);
|
column < this->columnCount_);
|
||||||
|
|
||||||
|
@ -207,6 +224,8 @@ public:
|
||||||
|
|
||||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||||
{
|
{
|
||||||
|
(void)parent;
|
||||||
|
|
||||||
if (count != 1)
|
if (count != 1)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -237,18 +256,22 @@ protected:
|
||||||
std::vector<QStandardItem *> &row,
|
std::vector<QStandardItem *> &row,
|
||||||
int proposedIndex)
|
int proposedIndex)
|
||||||
{
|
{
|
||||||
|
(void)item, (void)row;
|
||||||
|
|
||||||
return proposedIndex;
|
return proposedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void afterRemoved(const TVectorItem &item,
|
virtual void afterRemoved(const TVectorItem &item,
|
||||||
std::vector<QStandardItem *> &row, int index)
|
std::vector<QStandardItem *> &row, int index)
|
||||||
{
|
{
|
||||||
|
(void)item, (void)row, (void)index;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
int column, const QVariant &value, int role,
|
int column, const QVariant &value, int role,
|
||||||
int rowIndex)
|
int rowIndex)
|
||||||
{
|
{
|
||||||
|
(void)row, (void)column, (void)value, (void)role, (void)rowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/TwitchApi.hpp"
|
#include "providers/twitch/TwitchApi.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "controllers/notifications/NotificationModel.hpp"
|
#include "controllers/notifications/NotificationModel.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "providers/twitch/TwitchApi.hpp"
|
#include "providers/twitch/TwitchApi.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum class MessageFlag : uint32_t {
|
||||||
RecentMessage = (1 << 15),
|
RecentMessage = (1 << 15),
|
||||||
Whisper = (1 << 16),
|
Whisper = (1 << 16),
|
||||||
HighlightedWhisper = (1 << 17),
|
HighlightedWhisper = (1 << 17),
|
||||||
|
Debug = (1 << 18),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
||||||
{
|
{
|
||||||
backgroundColor = QColor("#404040");
|
backgroundColor = QColor("#404040");
|
||||||
}
|
}
|
||||||
|
else if (this->message_->flags.has(MessageFlag::Debug))
|
||||||
|
{
|
||||||
|
backgroundColor = QColor("#4A273D");
|
||||||
|
}
|
||||||
|
|
||||||
painter.fillRect(buffer->rect(), backgroundColor);
|
painter.fillRect(buffer->rect(), backgroundColor);
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,17 @@ const int MAX_FALLOFF_COUNTER = 60;
|
||||||
AbstractIrcServer::AbstractIrcServer()
|
AbstractIrcServer::AbstractIrcServer()
|
||||||
{
|
{
|
||||||
// Initialize the connections
|
// Initialize the connections
|
||||||
|
// XXX: don't create write connection if there is not separate write connection.
|
||||||
this->writeConnection_.reset(new IrcConnection);
|
this->writeConnection_.reset(new IrcConnection);
|
||||||
this->writeConnection_->moveToThread(
|
this->writeConnection_->moveToThread(
|
||||||
QCoreApplication::instance()->thread());
|
QCoreApplication::instance()->thread());
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||||
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
this, [this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
||||||
|
QObject::connect(
|
||||||
|
this->writeConnection_.get(), &Communi::IrcConnection::connected, this,
|
||||||
|
[this] { this->onWriteConnected(this->writeConnection_.get()); });
|
||||||
|
|
||||||
// Listen to read connection message signals
|
// Listen to read connection message signals
|
||||||
this->readConnection_.reset(new IrcConnection);
|
this->readConnection_.reset(new IrcConnection);
|
||||||
|
@ -32,21 +36,18 @@ AbstractIrcServer::AbstractIrcServer()
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||||
[this](auto msg) { this->readConnectionMessageReceived(msg); });
|
this, [this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||||
QObject::connect(this->readConnection_.get(),
|
QObject::connect(this->readConnection_.get(),
|
||||||
&Communi::IrcConnection::privateMessageReceived,
|
&Communi::IrcConnection::privateMessageReceived, this,
|
||||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->readConnection_.get(), &Communi::IrcConnection::connected,
|
this->readConnection_.get(), &Communi::IrcConnection::connected, this,
|
||||||
[this] { this->onReadConnected(this->readConnection_.get()); });
|
[this] { this->onReadConnected(this->readConnection_.get()); });
|
||||||
QObject::connect(
|
|
||||||
this->writeConnection_.get(), &Communi::IrcConnection::connected,
|
|
||||||
[this] { this->onWriteConnected(this->writeConnection_.get()); });
|
|
||||||
QObject::connect(this->readConnection_.get(),
|
QObject::connect(this->readConnection_.get(),
|
||||||
&Communi::IrcConnection::disconnected,
|
&Communi::IrcConnection::disconnected, this,
|
||||||
[this] { this->onDisconnected(); });
|
[this] { this->onDisconnected(); });
|
||||||
QObject::connect(this->readConnection_.get(),
|
QObject::connect(this->readConnection_.get(),
|
||||||
&Communi::IrcConnection::socketError,
|
&Communi::IrcConnection::socketError, this,
|
||||||
[this] { this->onSocketError(); });
|
[this] { this->onSocketError(); });
|
||||||
|
|
||||||
// listen to reconnect request
|
// listen to reconnect request
|
||||||
|
@ -75,37 +76,29 @@ void AbstractIrcServer::connect()
|
||||||
{
|
{
|
||||||
this->disconnect();
|
this->disconnect();
|
||||||
|
|
||||||
bool separateWriteConnection = this->hasSeparateWriteConnection();
|
if (this->hasSeparateWriteConnection())
|
||||||
|
|
||||||
if (separateWriteConnection)
|
|
||||||
{
|
{
|
||||||
this->initializeConnection(this->writeConnection_.get(), false, true);
|
this->initializeConnection(this->writeConnection_.get(), Write);
|
||||||
this->initializeConnection(this->readConnection_.get(), true, false);
|
this->initializeConnection(this->readConnection_.get(), Read);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->initializeConnection(this->readConnection_.get(), true, true);
|
this->initializeConnection(this->readConnection_.get(), Both);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fourtf: this should be asynchronous
|
void AbstractIrcServer::open(ConnectionType type)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->connectionMutex_);
|
||||||
|
|
||||||
|
if (type == Write)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock1(this->connectionMutex_);
|
|
||||||
std::lock_guard<std::mutex> lock2(this->channelMutex);
|
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
||||||
{
|
|
||||||
if (auto channel = std::shared_ptr<Channel>(weak.lock()))
|
|
||||||
{
|
|
||||||
this->readConnection_->sendRaw("JOIN #" + channel->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->writeConnection_->open();
|
this->writeConnection_->open();
|
||||||
|
}
|
||||||
|
if (type & Read)
|
||||||
|
{
|
||||||
this->readConnection_->open();
|
this->readConnection_->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this->onConnected();
|
|
||||||
// possbile event: started to connect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::disconnect()
|
void AbstractIrcServer::disconnect()
|
||||||
|
@ -113,7 +106,10 @@ void AbstractIrcServer::disconnect()
|
||||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||||
|
|
||||||
this->readConnection_->close();
|
this->readConnection_->close();
|
||||||
this->writeConnection_->close();
|
if (this->hasSeparateWriteConnection())
|
||||||
|
{
|
||||||
|
this->writeConnection_->close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::sendMessage(const QString &channelName,
|
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||||
|
@ -139,10 +135,10 @@ void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||||
void AbstractIrcServer::writeConnectionMessageReceived(
|
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||||
Communi::IrcMessage *message)
|
Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
|
(void)message;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
||||||
const QString &dirtyChannelName)
|
|
||||||
{
|
{
|
||||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||||
|
|
||||||
|
@ -162,26 +158,24 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||||
return Channel::getEmpty();
|
return Channel::getEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString clojuresInCppAreShit = channelName;
|
|
||||||
|
|
||||||
this->channels.insert(channelName, chan);
|
this->channels.insert(channelName, chan);
|
||||||
chan->destroyed.connect([this, clojuresInCppAreShit] {
|
this->connections_.emplace_back(chan->destroyed.connect([this,
|
||||||
|
channelName] {
|
||||||
// fourtf: issues when the server itself is destroyed
|
// fourtf: issues when the server itself is destroyed
|
||||||
|
|
||||||
log("[AbstractIrcServer::addChannel] {} was destroyed",
|
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
|
||||||
clojuresInCppAreShit);
|
this->channels.remove(channelName);
|
||||||
this->channels.remove(clojuresInCppAreShit);
|
|
||||||
|
|
||||||
if (this->readConnection_)
|
if (this->readConnection_)
|
||||||
{
|
{
|
||||||
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
this->readConnection_->sendRaw("PART #" + channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->writeConnection_)
|
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
||||||
{
|
{
|
||||||
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
|
this->writeConnection_->sendRaw("PART #" + channelName);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
// join irc channel
|
// join irc channel
|
||||||
{
|
{
|
||||||
|
@ -189,20 +183,25 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||||
|
|
||||||
if (this->readConnection_)
|
if (this->readConnection_)
|
||||||
{
|
{
|
||||||
this->readConnection_->sendRaw("JOIN #" + channelName);
|
if (this->readConnection_->isConnected())
|
||||||
|
{
|
||||||
|
this->readConnection_->sendRaw("JOIN #" + channelName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->writeConnection_)
|
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
||||||
{
|
{
|
||||||
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
if (this->readConnection_->isConnected())
|
||||||
|
{
|
||||||
|
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chan;
|
return chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
|
||||||
const QString &dirtyChannelName)
|
|
||||||
{
|
{
|
||||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||||
|
|
||||||
|
@ -230,10 +229,35 @@ std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||||
return Channel::getEmpty();
|
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 AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
(void)connection;
|
||||||
|
|
||||||
|
std::lock_guard lock(this->channelMutex);
|
||||||
|
|
||||||
|
// join channels
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
{
|
||||||
|
if (auto channel = weak.lock())
|
||||||
|
{
|
||||||
|
connection->sendRaw("JOIN #" + channel->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// connected/disconnected message
|
||||||
auto connectedMsg = makeSystemMessage("connected");
|
auto connectedMsg = makeSystemMessage("connected");
|
||||||
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||||
auto reconnected = makeSystemMessage("reconnected");
|
auto reconnected = makeSystemMessage("reconnected");
|
||||||
|
@ -267,6 +291,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||||
|
|
||||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||||
{
|
{
|
||||||
|
(void)connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::onDisconnected()
|
void AbstractIrcServer::onDisconnected()
|
||||||
|
@ -297,12 +322,16 @@ void AbstractIrcServer::onSocketError()
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||||
const QString &channelName)
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
|
(void)channelName;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||||
{
|
{
|
||||||
return dirtyChannelName;
|
if (dirtyChannelName.startsWith('#'))
|
||||||
|
return dirtyChannelName.mid(1);
|
||||||
|
else
|
||||||
|
return dirtyChannelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||||
|
@ -324,6 +353,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||||
void AbstractIrcServer::privateMessageReceived(
|
void AbstractIrcServer::privateMessageReceived(
|
||||||
Communi::IrcPrivateMessage *message)
|
Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
|
(void)message;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::readConnectionMessageReceived(
|
void AbstractIrcServer::readConnectionMessageReceived(
|
||||||
|
@ -337,7 +367,7 @@ void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||||
{
|
{
|
||||||
std::shared_ptr<Channel> chan = weak.lock();
|
ChannelPtr chan = weak.lock();
|
||||||
if (!chan)
|
if (!chan)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -13,9 +14,11 @@ namespace chatterino {
|
||||||
class Channel;
|
class Channel;
|
||||||
using ChannelPtr = std::shared_ptr<Channel>;
|
using ChannelPtr = std::shared_ptr<Channel>;
|
||||||
|
|
||||||
class AbstractIrcServer
|
class AbstractIrcServer : public QObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
|
||||||
|
|
||||||
virtual ~AbstractIrcServer() = default;
|
virtual ~AbstractIrcServer() = default;
|
||||||
|
|
||||||
// connection
|
// connection
|
||||||
|
@ -26,14 +29,13 @@ public:
|
||||||
void sendRawMessage(const QString &rawMessage);
|
void sendRawMessage(const QString &rawMessage);
|
||||||
|
|
||||||
// channels
|
// channels
|
||||||
std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName);
|
ChannelPtr getOrAddChannel(const QString &dirtyChannelName);
|
||||||
std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName);
|
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName);
|
||||||
|
std::vector<std::weak_ptr<Channel>> getChannels();
|
||||||
|
|
||||||
// signals
|
// signals
|
||||||
pajlada::Signals::NoArgSignal connected;
|
pajlada::Signals::NoArgSignal connected;
|
||||||
pajlada::Signals::NoArgSignal disconnected;
|
pajlada::Signals::NoArgSignal disconnected;
|
||||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
|
|
||||||
// onPrivateMessage;
|
|
||||||
|
|
||||||
void addFakeMessage(const QString &data);
|
void addFakeMessage(const QString &data);
|
||||||
|
|
||||||
|
@ -43,8 +45,8 @@ public:
|
||||||
protected:
|
protected:
|
||||||
AbstractIrcServer();
|
AbstractIrcServer();
|
||||||
|
|
||||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
virtual void initializeConnection(IrcConnection *connection,
|
||||||
bool isWrite) = 0;
|
ConnectionType type) = 0;
|
||||||
virtual std::shared_ptr<Channel> createChannel(
|
virtual std::shared_ptr<Channel> createChannel(
|
||||||
const QString &channelName) = 0;
|
const QString &channelName) = 0;
|
||||||
|
|
||||||
|
@ -63,14 +65,23 @@ protected:
|
||||||
virtual bool hasSeparateWriteConnection() const = 0;
|
virtual bool hasSeparateWriteConnection() const = 0;
|
||||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||||
|
|
||||||
|
void open(ConnectionType type);
|
||||||
|
|
||||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||||
std::mutex channelMutex;
|
std::mutex channelMutex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initConnection();
|
void initConnection();
|
||||||
|
|
||||||
std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
|
struct Deleter {
|
||||||
std::unique_ptr<IrcConnection> readConnection_ = nullptr;
|
void operator()(IrcConnection *conn)
|
||||||
|
{
|
||||||
|
conn->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
|
||||||
|
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
|
||||||
|
|
||||||
QTimer reconnectTimer_;
|
QTimer reconnectTimer_;
|
||||||
int falloffCounter_ = 1;
|
int falloffCounter_ = 1;
|
||||||
|
@ -78,6 +89,7 @@ private:
|
||||||
std::mutex connectionMutex_;
|
std::mutex connectionMutex_;
|
||||||
|
|
||||||
// bool autoReconnect_ = false;
|
// bool autoReconnect_ = false;
|
||||||
|
pajlada::Signals::SignalHolder connections_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
261
src/providers/irc/Irc2.cpp
Normal file
261
src/providers/irc/Irc2.cpp
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
#include "Irc2.hpp"
|
||||||
|
|
||||||
|
#include <pajlada/serialize.hpp>
|
||||||
|
#include "common/Credentials.hpp"
|
||||||
|
#include "common/SignalVectorModel.hpp"
|
||||||
|
#include "singletons/Paths.hpp"
|
||||||
|
#include "util/CombinePath.hpp"
|
||||||
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
#include "util/StandardItemHelper.hpp"
|
||||||
|
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString configPath()
|
||||||
|
{
|
||||||
|
return combinePath(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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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::getInstance().get("irc", getCredentialName(*this), receiver,
|
||||||
|
std::move(onLoaded));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrcServerData::setPassword(const QString &password)
|
||||||
|
{
|
||||||
|
Credentials::getInstance().set("irc", getCredentialName(*this), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
Irc::Irc()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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::getInstance().erase("irc",
|
||||||
|
getCredentialName(args.item));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections.delayedItemsChanged.connect([this] { this->save(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractTableModel *Irc::newConnectionModel(QObject *parent)
|
||||||
|
{
|
||||||
|
auto model = new Model(parent);
|
||||||
|
model->init(&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::getInstance()
|
||||||
|
{
|
||||||
|
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.appendItem(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
67
src/providers/irc/Irc2.hpp
Normal file
67
src/providers/irc/Irc2.hpp
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rapidjson/rapidjson.h>
|
||||||
|
#include <common/SignalVector.hpp>
|
||||||
|
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
|
|
||||||
|
class QAbstractTableModel;
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
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 &getInstance();
|
||||||
|
|
||||||
|
static inline void *const noEraseCredentialCaller =
|
||||||
|
reinterpret_cast<void *>(1);
|
||||||
|
|
||||||
|
UnsortedSignalVector<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,9 +1,68 @@
|
||||||
#include "IrcChannel2.hpp"
|
#include "IrcChannel2.hpp"
|
||||||
|
|
||||||
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/irc/IrcCommands.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// IrcChannel::IrcChannel()
|
IrcChannel::IrcChannel(const QString &name, IrcServer *server)
|
||||||
//{
|
: Channel(name, Channel::Type::Irc)
|
||||||
//}
|
, ChannelChatters(*static_cast<Channel *>(this))
|
||||||
//
|
, server_(server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrcChannel::sendMessage(const QString &message)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
if (message.startsWith("/"))
|
||||||
|
{
|
||||||
|
int 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())
|
||||||
|
this->server()->sendMessage(this->getName(), message);
|
||||||
|
|
||||||
|
MessageBuilder builder;
|
||||||
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TextElement>(this->server()->nick() + ":",
|
||||||
|
MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(message, MessageElementFlag::Text);
|
||||||
|
this->addMessage(builder.release());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IrcServer *IrcChannel::server()
|
||||||
|
{
|
||||||
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,11 +1,33 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/ChannelChatters.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// class IrcChannel
|
class Irc;
|
||||||
//{
|
class IrcServer;
|
||||||
// public:
|
|
||||||
// IrcChannel();
|
class IrcChannel : public Channel, public ChannelChatters
|
||||||
//};
|
{
|
||||||
//
|
public:
|
||||||
|
explicit IrcChannel(const QString &name, IrcServer *server);
|
||||||
|
|
||||||
|
void sendMessage(const QString &message) override;
|
||||||
|
|
||||||
|
// server may be nullptr
|
||||||
|
IrcServer *server();
|
||||||
|
|
||||||
|
// Channel methods
|
||||||
|
virtual bool canReconnect() const override;
|
||||||
|
virtual void reconnect() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setServer(IrcServer *server);
|
||||||
|
|
||||||
|
IrcServer *server_;
|
||||||
|
|
||||||
|
friend class Irc;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
76
src/providers/irc/IrcCommands.cpp
Normal file
76
src/providers/irc/IrcCommands.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "IrcCommands.hpp"
|
||||||
|
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
|
#include "util/Overloaded.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.addMessage(makeSystemMessage(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")
|
||||||
|
{
|
||||||
|
sendRaw("PRIVMSG " + 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 (paramsAfter(1).isEmpty())
|
||||||
|
sendRaw("KICK " + params[0] + " " + params[1]);
|
||||||
|
else
|
||||||
|
sendRaw("KICK " + params[0] + " " + params[1] + " :" +
|
||||||
|
paramsAfter(1));
|
||||||
|
}
|
||||||
|
else if (cmd == "wallops")
|
||||||
|
{
|
||||||
|
sendRaw("WALLOPS :" + allParams);
|
||||||
|
}
|
||||||
|
else if (cmd == "raw")
|
||||||
|
{
|
||||||
|
sendRaw(allParams);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendRaw(cmd.toUpper() + " " + allParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
12
src/providers/irc/IrcCommands.hpp
Normal file
12
src/providers/irc/IrcCommands.hpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class IrcChannel;
|
||||||
|
|
||||||
|
Outcome invokeIrcCommand(const QString &command, const QString ¶ms,
|
||||||
|
IrcChannel &channel);
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -13,7 +13,7 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||||
{
|
{
|
||||||
if (!this->recentlyReceivedMessage_.load())
|
if (!this->recentlyReceivedMessage_.load())
|
||||||
{
|
{
|
||||||
this->sendRaw("PING");
|
this->sendRaw("PING chatterino/ping");
|
||||||
this->reconnectTimer_.start();
|
this->reconnectTimer_.start();
|
||||||
}
|
}
|
||||||
this->recentlyReceivedMessage_ = false;
|
this->recentlyReceivedMessage_ = false;
|
||||||
|
|
|
@ -1,12 +1,260 @@
|
||||||
#include "IrcServer.hpp"
|
#include "IrcServer.hpp"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/irc/Irc2.hpp"
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "util/QObjectRef.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// IrcServer::IrcServer(const QString &hostname, int port)
|
IrcServer::IrcServer(const IrcServerData &data)
|
||||||
//{
|
: data_(new IrcServerData(data))
|
||||||
// this->initConnection();
|
{
|
||||||
//}
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
switch (this->data_->authType)
|
||||||
|
{
|
||||||
|
case IrcAuthType::Sasl:
|
||||||
|
connection->setSaslMechanism("PLAIN");
|
||||||
|
[[fallthrough]];
|
||||||
|
case IrcAuthType::Pass:
|
||||||
|
this->data_->getPassword(
|
||||||
|
this, [conn = new QObjectRef(connection) /* can't copy */,
|
||||||
|
this](const QString &password) mutable {
|
||||||
|
if (*conn)
|
||||||
|
{
|
||||||
|
(*conn)->setPassword(password);
|
||||||
|
this->open(Both);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete conn;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this->open(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->addMessage(makeSystemMessage(
|
||||||
|
QStringLiteral("Socket error: ") +
|
||||||
|
QAbstractSocket::staticMetaObject.enumerator(index)
|
||||||
|
.valueToKey(error)));
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
|
||||||
|
this, [](const QString &reserved, QString *result) {
|
||||||
|
*result = reserved + (std::rand() % 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
|
||||||
|
this, [this](Communi::IrcNoticeMessage *message) {
|
||||||
|
MessageBuilder builder;
|
||||||
|
|
||||||
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TextElement>(
|
||||||
|
message->nick(), MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(
|
||||||
|
"-> you:", MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(message->content(),
|
||||||
|
MessageElementFlag::Text);
|
||||||
|
|
||||||
|
auto msg = builder.release();
|
||||||
|
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
shared->addMessage(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
auto target = message->target();
|
||||||
|
target = target.startsWith('#') ? target.mid(1) : target;
|
||||||
|
|
||||||
|
if (auto channel = this->getChannelOrEmpty(target); !channel->isEmpty())
|
||||||
|
{
|
||||||
|
MessageBuilder builder;
|
||||||
|
|
||||||
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TextElement>(message->nick() + ":",
|
||||||
|
MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(message->content(),
|
||||||
|
MessageElementFlag::Text);
|
||||||
|
|
||||||
|
channel->addMessage(builder.release());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(this->cleanChannelName(x->channel()));
|
||||||
|
it != this->channels.end())
|
||||||
|
{
|
||||||
|
if (auto shared = it->lock())
|
||||||
|
{
|
||||||
|
if (message->nick() == this->data_->nick)
|
||||||
|
{
|
||||||
|
shared->addMessage(
|
||||||
|
MessageBuilder(systemMessage, "joined").release());
|
||||||
|
}
|
||||||
|
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(this->cleanChannelName(x->channel()));
|
||||||
|
it != this->channels.end())
|
||||||
|
{
|
||||||
|
if (auto shared = it->lock())
|
||||||
|
{
|
||||||
|
if (message->nick() == this->data_->nick)
|
||||||
|
{
|
||||||
|
shared->addMessage(
|
||||||
|
MessageBuilder(systemMessage, "parted").release());
|
||||||
|
}
|
||||||
|
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>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -5,20 +5,34 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// class IrcServer
|
struct IrcServerData;
|
||||||
//{
|
|
||||||
// public:
|
|
||||||
// IrcServer(const QString &hostname, int port);
|
|
||||||
|
|
||||||
// void setAccount(std::shared_ptr<IrcAccount> newAccount);
|
class IrcServer : public AbstractIrcServer
|
||||||
// std::shared_ptr<IrcAccount> getAccount() const;
|
{
|
||||||
|
public:
|
||||||
|
explicit IrcServer(const IrcServerData &data);
|
||||||
|
IrcServer(const IrcServerData &data,
|
||||||
|
const std::vector<std::weak_ptr<Channel>> &restoreChannels);
|
||||||
|
~IrcServer() override;
|
||||||
|
|
||||||
// protected:
|
int id();
|
||||||
// virtual void initializeConnection(Communi::IrcConnection *connection, bool
|
const QString &user();
|
||||||
// isReadConnection);
|
const QString &nick();
|
||||||
|
|
||||||
|
// AbstractIrcServer interface
|
||||||
|
protected:
|
||||||
|
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_;
|
||||||
|
};
|
||||||
|
|
||||||
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
|
||||||
// virtual void messageReceived(Communi::IrcMessage *message);
|
|
||||||
//};
|
|
||||||
//
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -20,7 +20,7 @@ protected:
|
||||||
QString chatroomOwnerId;
|
QString chatroomOwnerId;
|
||||||
QString chatroomOwnerName;
|
QString chatroomOwnerName;
|
||||||
|
|
||||||
friend class TwitchServer;
|
friend class TwitchIrcServer;
|
||||||
friend class TwitchMessageBuilder;
|
friend class TwitchMessageBuilder;
|
||||||
friend class IrcMessageHandler;
|
friend class IrcMessageHandler;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
#include "IrcMessageHandler.hpp"
|
#include "IrcMessageHandler.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "messages/LimitedQueue.hpp"
|
#include "messages/LimitedQueue.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchHelpers.hpp"
|
#include "providers/twitch/TwitchHelpers.hpp"
|
||||||
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
@ -85,7 +87,7 @@ std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
TwitchServer &server)
|
TwitchIrcServer &server)
|
||||||
{
|
{
|
||||||
this->addMessage(message, message->target(), message->content(), server,
|
this->addMessage(message, message->target(), message->content(), server,
|
||||||
false, message->isAction());
|
false, message->isAction());
|
||||||
|
@ -93,8 +95,9 @@ void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
|
|
||||||
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||||
const QString &target,
|
const QString &target,
|
||||||
const QString &content, TwitchServer &server,
|
const QString &content,
|
||||||
bool isSub, bool isAction)
|
TwitchIrcServer &server, bool isSub,
|
||||||
|
bool isAction)
|
||||||
{
|
{
|
||||||
QString channelName;
|
QString channelName;
|
||||||
if (!trimChannelName(target, channelName))
|
if (!trimChannelName(target, channelName))
|
||||||
|
@ -144,6 +147,10 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
chan->addMessage(msg);
|
chan->addMessage(msg);
|
||||||
|
if (auto chatters = dynamic_cast<ChannelChatters *>(chan.get()))
|
||||||
|
{
|
||||||
|
chatters->addRecentChatter(msg->displayName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,7 +446,7 @@ std::vector<MessagePtr> IrcMessageHandler::parseUserNoticeMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
TwitchServer &server)
|
TwitchIrcServer &server)
|
||||||
{
|
{
|
||||||
auto data = message->toData();
|
auto data = message->toData();
|
||||||
|
|
||||||
|
@ -591,7 +598,12 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||||
if (TwitchChannel *twitchChannel =
|
if (TwitchChannel *twitchChannel =
|
||||||
dynamic_cast<TwitchChannel *>(channel.get()))
|
dynamic_cast<TwitchChannel *>(channel.get()))
|
||||||
{
|
{
|
||||||
twitchChannel->addJoinedUser(message->nick());
|
if (message->nick() !=
|
||||||
|
getApp()->accounts->twitch.getCurrent()->getUserName() &&
|
||||||
|
getSettings()->showJoins.getValue())
|
||||||
|
{
|
||||||
|
twitchChannel->addJoinedUser(message->nick());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,7 +616,12 @@ void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
||||||
if (TwitchChannel *twitchChannel =
|
if (TwitchChannel *twitchChannel =
|
||||||
dynamic_cast<TwitchChannel *>(channel.get()))
|
dynamic_cast<TwitchChannel *>(channel.get()))
|
||||||
{
|
{
|
||||||
twitchChannel->addPartedUser(message->nick());
|
if (message->nick() !=
|
||||||
|
getApp()->accounts->twitch.getCurrent()->getUserName() &&
|
||||||
|
getSettings()->showJoins.getValue())
|
||||||
|
{
|
||||||
|
twitchChannel->addPartedUser(message->nick());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchServer;
|
class TwitchIrcServer;
|
||||||
class Channel;
|
class Channel;
|
||||||
|
|
||||||
class IrcMessageHandler
|
class IrcMessageHandler
|
||||||
|
@ -23,7 +23,7 @@ public:
|
||||||
std::vector<MessagePtr> parsePrivMessage(
|
std::vector<MessagePtr> parsePrivMessage(
|
||||||
Channel *channel, Communi::IrcPrivateMessage *message);
|
Channel *channel, Communi::IrcPrivateMessage *message);
|
||||||
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
TwitchServer &server);
|
TwitchIrcServer &server);
|
||||||
|
|
||||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||||
|
@ -36,7 +36,7 @@ public:
|
||||||
std::vector<MessagePtr> parseUserNoticeMessage(
|
std::vector<MessagePtr> parseUserNoticeMessage(
|
||||||
Channel *channel, Communi::IrcMessage *message);
|
Channel *channel, Communi::IrcMessage *message);
|
||||||
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
TwitchServer &server);
|
TwitchIrcServer &server);
|
||||||
|
|
||||||
void handleModeMessage(Communi::IrcMessage *message);
|
void handleModeMessage(Communi::IrcMessage *message);
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||||
const QString &content, TwitchServer &server, bool isResub,
|
const QString &content, TwitchIrcServer &server,
|
||||||
bool isAction);
|
bool isResub, bool isAction);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "providers/twitch/PubsubActions.hpp"
|
#include "providers/twitch/PubsubActions.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
|
@ -79,6 +79,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
TwitchBadges &globalTwitchBadges, BttvEmotes &bttv,
|
TwitchBadges &globalTwitchBadges, BttvEmotes &bttv,
|
||||||
FfzEmotes &ffz)
|
FfzEmotes &ffz)
|
||||||
: Channel(name, Channel::Type::Twitch)
|
: Channel(name, Channel::Type::Twitch)
|
||||||
|
, ChannelChatters(*static_cast<Channel *>(this))
|
||||||
, subscriptionUrl_("https://www.twitch.tv/subs/" + name)
|
, subscriptionUrl_("https://www.twitch.tv/subs/" + name)
|
||||||
, channelUrl_("https://twitch.tv/" + name)
|
, channelUrl_("https://twitch.tv/" + name)
|
||||||
, popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name)
|
, popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name)
|
||||||
|
@ -280,69 +281,14 @@ bool TwitchChannel::hasHighRateLimit() const
|
||||||
return this->isMod() || this->isBroadcaster() || this->isVIP();
|
return this->isMod() || this->isBroadcaster() || this->isVIP();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::addRecentChatter(const MessagePtr &message)
|
bool TwitchChannel::canReconnect() const
|
||||||
{
|
{
|
||||||
this->chatters_.access()->insert(message->displayName);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::addJoinedUser(const QString &user)
|
void TwitchChannel::reconnect()
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
getApp()->twitch.server->connect();
|
||||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
|
||||||
!getSettings()->showJoins.getValue())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto joinedUsers = this->joinedUsers_.access();
|
|
||||||
joinedUsers->append(user);
|
|
||||||
|
|
||||||
if (!this->joinedUsersMergeQueued_)
|
|
||||||
{
|
|
||||||
this->joinedUsersMergeQueued_ = true;
|
|
||||||
|
|
||||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
|
||||||
auto joinedUsers = this->joinedUsers_.access();
|
|
||||||
|
|
||||||
MessageBuilder builder(systemMessage,
|
|
||||||
"Users joined: " + joinedUsers->join(", "));
|
|
||||||
builder->flags.set(MessageFlag::Collapsed);
|
|
||||||
joinedUsers->clear();
|
|
||||||
this->addMessage(builder.release());
|
|
||||||
this->joinedUsersMergeQueued_ = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwitchChannel::addPartedUser(const QString &user)
|
|
||||||
{
|
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
|
||||||
!getSettings()->showJoins.getValue())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto partedUsers = this->partedUsers_.access();
|
|
||||||
partedUsers->append(user);
|
|
||||||
|
|
||||||
if (!this->partedUsersMergeQueued_)
|
|
||||||
{
|
|
||||||
this->partedUsersMergeQueued_ = true;
|
|
||||||
|
|
||||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
|
||||||
auto partedUsers = this->partedUsers_.access();
|
|
||||||
|
|
||||||
MessageBuilder builder(systemMessage,
|
|
||||||
"Users parted: " + partedUsers->join(", "));
|
|
||||||
builder->flags.set(MessageFlag::Collapsed);
|
|
||||||
this->addMessage(builder.release());
|
|
||||||
partedUsers->clear();
|
|
||||||
|
|
||||||
this->partedUsersMergeQueued_ = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TwitchChannel::roomId() const
|
QString TwitchChannel::roomId() const
|
||||||
|
@ -384,11 +330,6 @@ AccessGuard<const TwitchChannel::StreamStatus>
|
||||||
return this->streamStatus_.accessConst();
|
return this->streamStatus_.accessConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const UsernameSet> TwitchChannel::accessChatters() const
|
|
||||||
{
|
|
||||||
return this->chatters_.accessConst();
|
|
||||||
}
|
|
||||||
|
|
||||||
const TwitchBadges &TwitchChannel::globalTwitchBadges() const
|
const TwitchBadges &TwitchChannel::globalTwitchBadges() const
|
||||||
{
|
{
|
||||||
return this->globalTwitchBadges_;
|
return this->globalTwitchBadges_;
|
||||||
|
@ -691,7 +632,7 @@ void TwitchChannel::refreshChatters()
|
||||||
auto pair = parseChatters(result.parseJson());
|
auto pair = parseChatters(result.parseJson());
|
||||||
if (pair.first)
|
if (pair.first)
|
||||||
{
|
{
|
||||||
*this->chatters_.access() = std::move(pair.second);
|
this->setChatters(std::move(pair.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
return pair.first;
|
return pair.first;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/ChannelChatters.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
#include "common/UsernameSet.hpp"
|
||||||
|
@ -29,9 +30,11 @@ class TwitchBadges;
|
||||||
class FfzEmotes;
|
class FfzEmotes;
|
||||||
class BttvEmotes;
|
class BttvEmotes;
|
||||||
|
|
||||||
class TwitchServer;
|
class TwitchIrcServer;
|
||||||
|
|
||||||
class TwitchChannel : public Channel, pajlada::Signals::SignalHolder
|
class TwitchChannel : public Channel,
|
||||||
|
public ChannelChatters,
|
||||||
|
pajlada::Signals::SignalHolder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct StreamStatus {
|
struct StreamStatus {
|
||||||
|
@ -64,6 +67,8 @@ public:
|
||||||
bool isStaff() const;
|
bool isStaff() const;
|
||||||
virtual bool isBroadcaster() const override;
|
virtual bool isBroadcaster() const override;
|
||||||
virtual bool hasHighRateLimit() const override;
|
virtual bool hasHighRateLimit() const override;
|
||||||
|
virtual bool canReconnect() const override;
|
||||||
|
virtual void reconnect() override;
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const QString &subscriptionUrl();
|
const QString &subscriptionUrl();
|
||||||
|
@ -73,7 +78,6 @@ public:
|
||||||
QString roomId() const;
|
QString roomId() const;
|
||||||
AccessGuard<const RoomModes> accessRoomModes() const;
|
AccessGuard<const RoomModes> accessRoomModes() const;
|
||||||
AccessGuard<const StreamStatus> accessStreamStatus() const;
|
AccessGuard<const StreamStatus> accessStreamStatus() const;
|
||||||
AccessGuard<const UsernameSet> accessChatters() const;
|
|
||||||
|
|
||||||
// Emotes
|
// Emotes
|
||||||
const TwitchBadges &globalTwitchBadges() const;
|
const TwitchBadges &globalTwitchBadges() const;
|
||||||
|
@ -101,9 +105,6 @@ public:
|
||||||
pajlada::Signals::NoArgSignal liveStatusChanged;
|
pajlada::Signals::NoArgSignal liveStatusChanged;
|
||||||
pajlada::Signals::NoArgSignal roomModesChanged;
|
pajlada::Signals::NoArgSignal roomModesChanged;
|
||||||
|
|
||||||
protected:
|
|
||||||
void addRecentChatter(const MessagePtr &message) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct NameOptions {
|
struct NameOptions {
|
||||||
QString displayName;
|
QString displayName;
|
||||||
|
@ -125,8 +126,6 @@ private:
|
||||||
void refreshCheerEmotes();
|
void refreshCheerEmotes();
|
||||||
void loadRecentMessages();
|
void loadRecentMessages();
|
||||||
|
|
||||||
void addJoinedUser(const QString &user);
|
|
||||||
void addPartedUser(const QString &user);
|
|
||||||
void setLive(bool newLiveStatus);
|
void setLive(bool newLiveStatus);
|
||||||
void setMod(bool value);
|
void setMod(bool value);
|
||||||
void setVIP(bool value);
|
void setVIP(bool value);
|
||||||
|
@ -140,7 +139,6 @@ private:
|
||||||
const QString popoutPlayerUrl_;
|
const QString popoutPlayerUrl_;
|
||||||
UniqueAccess<StreamStatus> streamStatus_;
|
UniqueAccess<StreamStatus> streamStatus_;
|
||||||
UniqueAccess<RoomModes> roomModes_;
|
UniqueAccess<RoomModes> roomModes_;
|
||||||
UniqueAccess<UsernameSet> chatters_; // maps 2 char prefix to set of names
|
|
||||||
|
|
||||||
// Emotes
|
// Emotes
|
||||||
TwitchBadges &globalTwitchBadges_;
|
TwitchBadges &globalTwitchBadges_;
|
||||||
|
@ -163,18 +161,13 @@ private:
|
||||||
bool staff_ = false;
|
bool staff_ = false;
|
||||||
UniqueAccess<QString> roomID_;
|
UniqueAccess<QString> roomID_;
|
||||||
|
|
||||||
UniqueAccess<QStringList> joinedUsers_;
|
|
||||||
bool joinedUsersMergeQueued_ = false;
|
|
||||||
UniqueAccess<QStringList> partedUsers_;
|
|
||||||
bool partedUsersMergeQueued_ = false;
|
|
||||||
|
|
||||||
// --
|
// --
|
||||||
QString lastSentMessage_;
|
QString lastSentMessage_;
|
||||||
QObject lifetimeGuard_;
|
QObject lifetimeGuard_;
|
||||||
QTimer liveStatusTimer_;
|
QTimer liveStatusTimer_;
|
||||||
QTimer chattersListTimer_;
|
QTimer chattersListTimer_;
|
||||||
|
|
||||||
friend class TwitchServer;
|
friend class TwitchIrcServer;
|
||||||
friend class TwitchMessageBuilder;
|
friend class TwitchMessageBuilder;
|
||||||
friend class IrcMessageHandler;
|
friend class IrcMessageHandler;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "TwitchServer.hpp"
|
#include "TwitchIrcServer.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
@ -38,13 +38,11 @@ namespace {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TwitchServer::TwitchServer()
|
TwitchIrcServer::TwitchIrcServer()
|
||||||
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
||||||
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
||||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||||
{
|
{
|
||||||
qDebug() << "init TwitchServer";
|
|
||||||
|
|
||||||
this->pubsub = new PubSub;
|
this->pubsub = new PubSub;
|
||||||
|
|
||||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
||||||
|
@ -53,7 +51,7 @@ TwitchServer::TwitchServer()
|
||||||
// false);
|
// false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::initialize(Settings &settings, Paths &paths)
|
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
|
||||||
{
|
{
|
||||||
getApp()->accounts->twitch.currentUserChanged.connect(
|
getApp()->accounts->twitch.currentUserChanged.connect(
|
||||||
[this]() { postToThread([this] { this->connect(); }); });
|
[this]() { postToThread([this] { this->connect(); }); });
|
||||||
|
@ -63,11 +61,9 @@ void TwitchServer::initialize(Settings &settings, Paths &paths)
|
||||||
this->ffz.loadEmotes();
|
this->ffz.loadEmotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
||||||
bool isWrite)
|
ConnectionType type)
|
||||||
{
|
{
|
||||||
this->singleConnection_ = isRead == isWrite;
|
|
||||||
|
|
||||||
std::shared_ptr<TwitchAccount> account =
|
std::shared_ptr<TwitchAccount> account =
|
||||||
getApp()->accounts->twitch.getCurrent();
|
getApp()->accounts->twitch.getCurrent();
|
||||||
|
|
||||||
|
@ -97,9 +93,12 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
||||||
connection->setHost("irc.chat.twitch.tv");
|
connection->setHost("irc.chat.twitch.tv");
|
||||||
connection->setPort(6697);
|
connection->setPort(6697);
|
||||||
|
|
||||||
|
this->open(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
||||||
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
std::shared_ptr<TwitchChannel> channel;
|
std::shared_ptr<TwitchChannel> channel;
|
||||||
if (isChatroom(channelName))
|
if (isChatroom(channelName))
|
||||||
|
@ -123,13 +122,17 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
||||||
return std::shared_ptr<Channel>(channel);
|
return std::shared_ptr<Channel>(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
void TwitchIrcServer::privateMessageReceived(
|
||||||
|
Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
void TwitchIrcServer::readConnectionMessageReceived(
|
||||||
|
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
|
||||||
|
@ -155,7 +158,8 @@ void TwitchServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
void TwitchIrcServer::writeConnectionMessageReceived(
|
||||||
|
Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
const QString &command = message->command();
|
const QString &command = message->command();
|
||||||
|
|
||||||
|
@ -193,7 +197,7 @@ void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::onReadConnected(IrcConnection *connection)
|
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
||||||
{
|
{
|
||||||
AbstractIrcServer::onReadConnected(connection);
|
AbstractIrcServer::onReadConnected(connection);
|
||||||
|
|
||||||
|
@ -202,7 +206,7 @@ void TwitchServer::onReadConnected(IrcConnection *connection)
|
||||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::onWriteConnected(IrcConnection *connection)
|
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
|
||||||
{
|
{
|
||||||
AbstractIrcServer::onWriteConnected(connection);
|
AbstractIrcServer::onWriteConnected(connection);
|
||||||
|
|
||||||
|
@ -211,7 +215,7 @@ void TwitchServer::onWriteConnected(IrcConnection *connection)
|
||||||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
||||||
const QString &channelName)
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
if (channelName == "/whispers")
|
if (channelName == "/whispers")
|
||||||
|
@ -249,7 +253,7 @@ std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::forEachChannelAndSpecialChannels(
|
void TwitchIrcServer::forEachChannelAndSpecialChannels(
|
||||||
std::function<void(ChannelPtr)> func)
|
std::function<void(ChannelPtr)> func)
|
||||||
{
|
{
|
||||||
this->forEachChannel(func);
|
this->forEachChannel(func);
|
||||||
|
@ -258,7 +262,7 @@ void TwitchServer::forEachChannelAndSpecialChannels(
|
||||||
func(this->mentionsChannel);
|
func(this->mentionsChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
|
||||||
const QString &channelId)
|
const QString &channelId)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||||
|
@ -283,19 +287,22 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||||
return Channel::getEmpty();
|
return Channel::getEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TwitchServer::cleanChannelName(const QString &dirtyChannelName)
|
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||||
{
|
{
|
||||||
return dirtyChannelName.toLower();
|
if (dirtyChannelName.startsWith('#'))
|
||||||
|
return dirtyChannelName.mid(1).toLower();
|
||||||
|
else
|
||||||
|
return dirtyChannelName.toLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TwitchServer::hasSeparateWriteConnection() const
|
bool TwitchIrcServer::hasSeparateWriteConnection() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
// return getSettings()->twitchSeperateWriteConnection;
|
// return getSettings()->twitchSeperateWriteConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
|
||||||
const QString &message, bool &sent)
|
const QString &message, bool &sent)
|
||||||
{
|
{
|
||||||
sent = false;
|
sent = false;
|
||||||
|
|
||||||
|
@ -354,11 +361,11 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||||
sent = true;
|
sent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BttvEmotes &TwitchServer::getBttvEmotes() const
|
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
|
||||||
{
|
{
|
||||||
return this->bttv;
|
return this->bttv;
|
||||||
}
|
}
|
||||||
const FfzEmotes &TwitchServer::getFfzEmotes() const
|
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
|
||||||
{
|
{
|
||||||
return this->ffz;
|
return this->ffz;
|
||||||
}
|
}
|
|
@ -20,11 +20,11 @@ class Paths;
|
||||||
class PubSub;
|
class PubSub;
|
||||||
class TwitchChannel;
|
class TwitchChannel;
|
||||||
|
|
||||||
class TwitchServer final : public AbstractIrcServer, public Singleton
|
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TwitchServer();
|
TwitchIrcServer();
|
||||||
virtual ~TwitchServer() override = default;
|
virtual ~TwitchIrcServer() override = default;
|
||||||
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ public:
|
||||||
const FfzEmotes &getFfzEmotes() const;
|
const FfzEmotes &getFfzEmotes() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
virtual void initializeConnection(IrcConnection *connection,
|
||||||
bool isWrite) override;
|
ConnectionType type) override;
|
||||||
virtual std::shared_ptr<Channel> createChannel(
|
virtual std::shared_ptr<Channel> createChannel(
|
||||||
const QString &channelName) override;
|
const QString &channelName) override;
|
||||||
|
|
||||||
|
@ -75,7 +75,6 @@ private:
|
||||||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||||
|
|
||||||
bool singleConnection_ = false;
|
|
||||||
TwitchBadges twitchBadges;
|
TwitchBadges twitchBadges;
|
||||||
BttvEmotes bttv;
|
BttvEmotes bttv;
|
||||||
FfzEmotes ffz;
|
FfzEmotes ffz;
|
|
@ -10,7 +10,7 @@
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ void Paths::initSubDirectories()
|
||||||
this->messageLogDirectory = makePath("Logs");
|
this->messageLogDirectory = makePath("Logs");
|
||||||
this->miscDirectory = makePath("Misc");
|
this->miscDirectory = makePath("Misc");
|
||||||
this->twitchProfileAvatars = makePath("ProfileAvatars");
|
this->twitchProfileAvatars = makePath("ProfileAvatars");
|
||||||
QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
//QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
||||||
}
|
}
|
||||||
|
|
||||||
Paths *getPaths()
|
Paths *getPaths()
|
||||||
|
|
|
@ -199,6 +199,10 @@ public:
|
||||||
|
|
||||||
/// Misc
|
/// Misc
|
||||||
BoolSetting betaUpdates = {"/misc/beta", false};
|
BoolSetting betaUpdates = {"/misc/beta", false};
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
||||||
|
#endif
|
||||||
|
|
||||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||||
|
@ -208,6 +212,15 @@ public:
|
||||||
|
|
||||||
QStringSetting cachePath = {"/cache/path", ""};
|
QStringSetting cachePath = {"/cache/path", ""};
|
||||||
|
|
||||||
|
/// Debug
|
||||||
|
BoolSetting showUnhandledIrcMessages = {"/debug/showUnhandledIrcMessages",
|
||||||
|
false};
|
||||||
|
|
||||||
|
/// UI
|
||||||
|
// Purely QOL settings are here (like last item in a list).
|
||||||
|
IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 0};
|
||||||
|
IntSetting lastSelectIrcConn = {"/ui/lastSelectIrcConn", 0};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateModerationActions();
|
void updateModerationActions();
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
#include "widgets/helper/CommonTexts.hpp"
|
#include "widgets/helper/CommonTexts.hpp"
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/irc/Irc2.hpp"
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -611,6 +614,20 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
|
||||||
obj.insert("type", "whispers");
|
obj.insert("type", "whispers");
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,6 +655,11 @@ IndirectChannel WindowManager::decodeChannel(const QJsonObject &obj)
|
||||||
{
|
{
|
||||||
return app->twitch.server->whispersChannel;
|
return app->twitch.server->whispersChannel;
|
||||||
}
|
}
|
||||||
|
else if (type == "irc")
|
||||||
|
{
|
||||||
|
return Irc::getInstance().getOrAddChannel(
|
||||||
|
obj.value("server").toInt(-1), obj.value("channel").toString());
|
||||||
|
}
|
||||||
|
|
||||||
return Channel::getEmpty();
|
return Channel::getEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,20 @@ public:
|
||||||
return LayoutCreator<T2>(item);
|
return LayoutCreator<T2>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Slot, typename Func>
|
||||||
|
LayoutCreator<T> connect(Slot slot, QObject *receiver, Func func)
|
||||||
|
{
|
||||||
|
QObject::connect(this->getElement(), slot, receiver, func);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
LayoutCreator<T> onClick(QObject *receiver, Func func)
|
||||||
|
{
|
||||||
|
QObject::connect(this->getElement(), &T::clicked, receiver, func);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T *item_;
|
T *item_;
|
||||||
|
|
||||||
|
@ -169,4 +183,12 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
LayoutCreator<T> makeDialog(Args &&... args)
|
||||||
|
{
|
||||||
|
T *t = new T(std::forward<Args>(args)...);
|
||||||
|
t->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
return LayoutCreator<T>(t);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
13
src/util/Overloaded.hpp
Normal file
13
src/util/Overloaded.hpp
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
template <class... Ts>
|
||||||
|
struct Overloaded : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... Ts>
|
||||||
|
Overloaded(Ts...)->Overloaded<Ts...>;
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -62,8 +62,9 @@ private:
|
||||||
if (other)
|
if (other)
|
||||||
{
|
{
|
||||||
this->conn_ =
|
this->conn_ =
|
||||||
QObject::connect(other, &QObject::destroyed,
|
QObject::connect(other, &QObject::destroyed, qApp,
|
||||||
[this](QObject *) { this->set(nullptr); });
|
[this](QObject *) { this->set(nullptr); },
|
||||||
|
Qt::DirectConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->t_ = other;
|
this->t_ = other;
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace chatterino {
|
||||||
static void setBoolItem(QStandardItem *item, bool value,
|
static void setBoolItem(QStandardItem *item, bool value,
|
||||||
bool userCheckable = true, bool selectable = true)
|
bool userCheckable = true, bool selectable = true)
|
||||||
{
|
{
|
||||||
item->setFlags((Qt::ItemFlags)(
|
item->setFlags(Qt::ItemFlags(
|
||||||
Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) |
|
Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) |
|
||||||
(userCheckable ? Qt::ItemIsUserCheckable : 0)));
|
(userCheckable ? Qt::ItemIsUserCheckable : 0)));
|
||||||
item->setCheckState(value ? Qt::Checked : Qt::Unchecked);
|
item->setCheckState(value ? Qt::Checked : Qt::Unchecked);
|
||||||
|
@ -17,15 +17,15 @@ static void setStringItem(QStandardItem *item, const QString &value,
|
||||||
bool editable = true, bool selectable = true)
|
bool editable = true, bool selectable = true)
|
||||||
{
|
{
|
||||||
item->setData(value, Qt::EditRole);
|
item->setData(value, Qt::EditRole);
|
||||||
item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled |
|
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled |
|
||||||
(selectable ? Qt::ItemIsSelectable : 0) |
|
(selectable ? Qt::ItemIsSelectable : 0) |
|
||||||
(editable ? (Qt::ItemIsEditable) : 0)));
|
(editable ? (Qt::ItemIsEditable) : 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static QStandardItem *emptyItem()
|
static QStandardItem *emptyItem()
|
||||||
{
|
{
|
||||||
auto *item = new QStandardItem();
|
auto *item = new QStandardItem();
|
||||||
item->setFlags((Qt::ItemFlags)0);
|
item->setFlags(Qt::ItemFlags(0));
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "common/Credentials.hpp"
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/Updates.hpp"
|
#include "singletons/Updates.hpp"
|
||||||
|
@ -108,7 +109,7 @@ bool Window::event(QEvent *event)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:;
|
default:;
|
||||||
};
|
}
|
||||||
|
|
||||||
return BaseWindow::event(event);
|
return BaseWindow::event(event);
|
||||||
}
|
}
|
||||||
|
|
97
src/widgets/dialogs/IrcConnectionEditor.cpp
Normal file
97
src/widgets/dialogs/IrcConnectionEditor.cpp
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#include "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
|
32
src/widgets/dialogs/IrcConnectionEditor.hpp
Normal file
32
src/widgets/dialogs/IrcConnectionEditor.hpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include "providers/irc/Irc2.hpp"
|
||||||
|
#include "widgets/BaseWindow.hpp"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class IrcConnectionEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct IrcServerData;
|
||||||
|
|
||||||
|
class IrcConnectionEditor : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false,
|
||||||
|
QWidget *parent = nullptr);
|
||||||
|
~IrcConnectionEditor();
|
||||||
|
|
||||||
|
IrcServerData data();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::IrcConnectionEditor *ui_;
|
||||||
|
IrcServerData data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
261
src/widgets/dialogs/IrcConnectionEditor.ui
Normal file
261
src/widgets/dialogs/IrcConnectionEditor.ui
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
<?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>
|
|
@ -1,10 +1,11 @@
|
||||||
#include "SelectChannelDialog.hpp"
|
#include "SelectChannelDialog.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
#include "widgets/dialogs/IrcConnectionEditor.hpp"
|
||||||
#include "widgets/helper/NotebookTab.hpp"
|
#include "widgets/helper/NotebookTab.hpp"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
@ -14,7 +15,12 @@
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <QTableView>
|
||||||
|
#include "providers/irc/Irc2.hpp"
|
||||||
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
|
|
||||||
#define TAB_TWITCH 0
|
#define TAB_TWITCH 0
|
||||||
|
#define TAB_IRC 1
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -122,21 +128,69 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// irc
|
// irc
|
||||||
/*
|
{
|
||||||
|
LayoutCreator<QWidget> obj(new QWidget());
|
||||||
|
auto outerBox = obj.setLayoutType<QFormLayout>();
|
||||||
|
|
||||||
{
|
{
|
||||||
LayoutCreator<QWidget> obj(new QWidget());
|
auto view = this->ui_.irc.servers = new EditableModelView(
|
||||||
auto vbox = obj.setLayoutType<QVBoxLayout>();
|
Irc::getInstance().newConnectionModel(this));
|
||||||
auto form = vbox.emplace<QFormLayout>();
|
|
||||||
|
|
||||||
form->addRow(new QLabel("User name:"), new QLineEdit());
|
view->setTitles({"host", "port", "ssl", "user", "nick", "real",
|
||||||
form->addRow(new QLabel("First nick choice:"), new QLineEdit());
|
"password", "login command"});
|
||||||
form->addRow(new QLabel("Second nick choice:"), new QLineEdit());
|
view->getTableView()->horizontalHeader()->resizeSection(0, 140);
|
||||||
form->addRow(new QLabel("Third nick choice:"), new QLineEdit());
|
|
||||||
|
|
||||||
auto tab = notebook->addPage(obj.getElement());
|
view->getTableView()->horizontalHeader()->setSectionHidden(1, true);
|
||||||
tab->setCustomTitle("Irc");
|
view->getTableView()->horizontalHeader()->setSectionHidden(2, true);
|
||||||
|
view->getTableView()->horizontalHeader()->setSectionHidden(4, true);
|
||||||
|
view->getTableView()->horizontalHeader()->setSectionHidden(5, true);
|
||||||
|
|
||||||
|
view->addButtonPressed.connect([] {
|
||||||
|
auto unique = IrcServerData{};
|
||||||
|
unique.id = Irc::getInstance().uniqueId();
|
||||||
|
|
||||||
|
auto editor = new IrcConnectionEditor(unique);
|
||||||
|
if (editor->exec() == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
Irc::getInstance().connections.appendItem(editor->data());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
view->getTableView(), &QTableView::doubleClicked,
|
||||||
|
[](const QModelIndex &index) {
|
||||||
|
auto editor = new IrcConnectionEditor(
|
||||||
|
Irc::getInstance()
|
||||||
|
.connections.getVector()[size_t(index.row())]);
|
||||||
|
|
||||||
|
if (editor->exec() == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
auto data = editor->data();
|
||||||
|
auto &&conns =
|
||||||
|
Irc::getInstance().connections.getVector();
|
||||||
|
int i = 0;
|
||||||
|
for (auto &&conn : conns)
|
||||||
|
{
|
||||||
|
if (conn.id == data.id)
|
||||||
|
{
|
||||||
|
Irc::getInstance().connections.removeItem(
|
||||||
|
i, Irc::noEraseCredentialCaller);
|
||||||
|
Irc::getInstance().connections.insertItem(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)");
|
||||||
|
}
|
||||||
|
|
||||||
layout->setStretchFactor(notebook.getElement(), 1);
|
layout->setStretchFactor(notebook.getElement(), 1);
|
||||||
|
|
||||||
|
@ -151,7 +205,7 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
[=](bool) { this->close(); });
|
[=](bool) { this->close(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
this->setScaleIndependantSize(300, 310);
|
this->setMinimumSize(300, 310);
|
||||||
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
||||||
this->ui_.twitch.channel->setFocus();
|
this->ui_.twitch.channel->setFocus();
|
||||||
|
|
||||||
|
@ -161,10 +215,24 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
||||||
auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this);
|
auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this);
|
||||||
QObject::connect(shortcut_cancel, &QShortcut::activated,
|
QObject::connect(shortcut_cancel, &QShortcut::activated,
|
||||||
[=] { this->close(); });
|
[=] { this->close(); });
|
||||||
|
|
||||||
|
// restore ui state
|
||||||
|
this->ui_.notebook->selectIndex(getSettings()->lastSelectChannelTab);
|
||||||
|
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
|
||||||
this->hasSelectedChannel_ = true;
|
this->hasSelectedChannel_ = true;
|
||||||
this->close();
|
this->close();
|
||||||
}
|
}
|
||||||
|
@ -204,6 +272,32 @@ void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel)
|
||||||
this->ui_.twitch.whispers->setFocus();
|
this->ui_.twitch.whispers->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::getInstance().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);
|
||||||
|
@ -245,6 +339,27 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const
|
||||||
return app->twitch.server->whispersChannel;
|
return app->twitch.server->whispersChannel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case TAB_IRC:
|
||||||
|
{
|
||||||
|
int row = this->ui_.irc.servers->getTableView()
|
||||||
|
->selectionModel()
|
||||||
|
->currentIndex()
|
||||||
|
.row();
|
||||||
|
|
||||||
|
auto &&vector = Irc::getInstance().connections.getVector();
|
||||||
|
|
||||||
|
if (row >= 0 && row < int(vector.size()))
|
||||||
|
{
|
||||||
|
return Irc::getInstance().getOrAddChannel(
|
||||||
|
vector[size_t(row)].id, this->ui_.irc.channel->text());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Channel::getEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this->selectedChannel_;
|
return this->selectedChannel_;
|
||||||
|
@ -258,7 +373,7 @@ bool SelectChannelDialog::hasSeletedChannel() const
|
||||||
bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
|
bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
|
||||||
QEvent *event)
|
QEvent *event)
|
||||||
{
|
{
|
||||||
auto *widget = (QWidget *)watched;
|
auto *widget = static_cast<QWidget *>(watched);
|
||||||
|
|
||||||
if (event->type() == QEvent::FocusIn)
|
if (event->type() == QEvent::FocusIn)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Notebook;
|
class Notebook;
|
||||||
|
class EditableModelView;
|
||||||
|
|
||||||
class SelectChannelDialog final : public BaseWindow
|
class SelectChannelDialog final : public BaseWindow
|
||||||
{
|
{
|
||||||
|
@ -47,6 +48,10 @@ private:
|
||||||
QRadioButton *mentions;
|
QRadioButton *mentions;
|
||||||
QRadioButton *watching;
|
QRadioButton *watching;
|
||||||
} twitch;
|
} twitch;
|
||||||
|
struct {
|
||||||
|
QLineEdit *channel;
|
||||||
|
EditableModelView *servers;
|
||||||
|
} irc;
|
||||||
} ui_;
|
} ui_;
|
||||||
|
|
||||||
EventFilter tabFilter_;
|
EventFilter tabFilter_;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "messages/layouts/MessageLayout.hpp"
|
#include "messages/layouts/MessageLayout.hpp"
|
||||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/TooltipPreviewImage.hpp"
|
#include "singletons/TooltipPreviewImage.hpp"
|
||||||
|
|
|
@ -98,6 +98,9 @@ AboutPage::AboutPage()
|
||||||
addLicense(form.getElement(), "Websocketpp",
|
addLicense(form.getElement(), "Websocketpp",
|
||||||
"https://www.zaphoyd.com/websocketpp/",
|
"https://www.zaphoyd.com/websocketpp/",
|
||||||
":/licenses/websocketpp.txt");
|
":/licenses/websocketpp.txt");
|
||||||
|
addLicense(form.getElement(), "QtKeychain",
|
||||||
|
"https://github.com/frankosterfeld/qtkeychain",
|
||||||
|
":/licenses/qtkeychain.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attributions = layout.emplace<QGroupBox>("Attributions...");
|
auto attributions = layout.emplace<QGroupBox>("Attributions...");
|
||||||
|
|
|
@ -383,6 +383,16 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
layout.addCheckbox("Open links in incognito/private mode",
|
layout.addCheckbox("Open links in incognito/private mode",
|
||||||
s.openLinksIncognito);
|
s.openLinksIncognito);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (!getPaths()->isPortable())
|
||||||
|
{
|
||||||
|
layout.addCheckbox(
|
||||||
|
"Use libsecret/KWallet/Gnome keychain to secure passwords",
|
||||||
|
s.useKeyring);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
layout.addCheckbox("Show moderation messages", s.hideModerationActions,
|
layout.addCheckbox("Show moderation messages", s.hideModerationActions,
|
||||||
true);
|
true);
|
||||||
layout.addCheckbox("Random username color for users who never set a color",
|
layout.addCheckbox("Random username color for users who never set a color",
|
||||||
|
@ -419,6 +429,9 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
layout.addCheckbox("Load message history on connect",
|
layout.addCheckbox("Load message history on connect",
|
||||||
s.loadTwitchMessageHistoryOnConnect);
|
s.loadTwitchMessageHistoryOnConnect);
|
||||||
|
|
||||||
|
layout.addCheckbox("Show unhandled irc messages",
|
||||||
|
s.showUnhandledIrcMessages);
|
||||||
|
|
||||||
layout.addTitle("Cache");
|
layout.addTitle("Cache");
|
||||||
layout.addDescription(
|
layout.addDescription(
|
||||||
"Files that are used often (such as emotes) are saved to disk to "
|
"Files that are used often (such as emotes) are saved to disk to "
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include "providers/twitch/EmoteValue.hpp"
|
#include "providers/twitch/EmoteValue.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "controllers/pings/PingController.hpp"
|
#include "controllers/pings/PingController.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
|
@ -211,9 +211,12 @@ void SplitHeader::initializeLayout()
|
||||||
}),
|
}),
|
||||||
// dropdown
|
// dropdown
|
||||||
this->dropdownButton_ = makeWidget<Button>([&](auto w) {
|
this->dropdownButton_ = makeWidget<Button>([&](auto w) {
|
||||||
auto menu = this->createMainMenu();
|
/// XXX: this never gets disconnected
|
||||||
this->mainMenu_ = menu.get();
|
this->split_->channelChanged.connect([this] {
|
||||||
w->setMenu(std::move(menu));
|
auto menu = this->createMainMenu();
|
||||||
|
this->mainMenu_ = menu.get();
|
||||||
|
this->dropdownButton_->setMenu(std::move(menu));
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
// add split
|
// add split
|
||||||
this->addButton_ = makeWidget<Button>([&](auto w) {
|
this->addButton_ = makeWidget<Button>([&](auto w) {
|
||||||
|
@ -275,13 +278,17 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
menu->addAction(OPEN_IN_BROWSER, this->split_, &Split::openInBrowser);
|
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||||
|
{
|
||||||
|
menu->addAction(OPEN_IN_BROWSER, this->split_, &Split::openInBrowser);
|
||||||
#ifndef USEWEBENGINE
|
#ifndef USEWEBENGINE
|
||||||
menu->addAction(OPEN_PLAYER_IN_BROWSER, this->split_,
|
menu->addAction(OPEN_PLAYER_IN_BROWSER, this->split_,
|
||||||
&Split::openBrowserPlayer);
|
&Split::openBrowserPlayer);
|
||||||
#endif
|
#endif
|
||||||
menu->addAction(OPEN_IN_STREAMLINK, this->split_, &Split::openInStreamlink);
|
menu->addAction(OPEN_IN_STREAMLINK, this->split_,
|
||||||
menu->addSeparator();
|
&Split::openInStreamlink);
|
||||||
|
menu->addSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// "How to..." sub menu
|
// "How to..." sub menu
|
||||||
|
@ -294,26 +301,30 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||||
// sub menu
|
// sub menu
|
||||||
auto moreMenu = new QMenu("More", this);
|
auto moreMenu = new QMenu("More", this);
|
||||||
|
|
||||||
moreMenu->addAction("Show viewer list", this->split_,
|
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||||
&Split::showViewerList);
|
{
|
||||||
|
moreMenu->addAction("Show viewer list", this->split_,
|
||||||
|
&Split::showViewerList);
|
||||||
|
|
||||||
moreMenu->addAction("Subscribe", this->split_, &Split::openSubPage);
|
moreMenu->addAction("Subscribe", this->split_, &Split::openSubPage);
|
||||||
|
|
||||||
auto action = new QAction(this);
|
auto action = new QAction(this);
|
||||||
action->setText("Notify when live");
|
action->setText("Notify when live");
|
||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
|
|
||||||
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
|
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
|
||||||
action->setChecked(getApp()->notifications->isChannelNotified(
|
action->setChecked(getApp()->notifications->isChannelNotified(
|
||||||
this->split_->getChannel()->getName(), Platform::Twitch));
|
this->split_->getChannel()->getName(), Platform::Twitch));
|
||||||
});
|
});
|
||||||
action->connect(action, &QAction::triggered, this, [this]() {
|
action->connect(action, &QAction::triggered, this, [this]() {
|
||||||
getApp()->notifications->updateChannelNotification(
|
getApp()->notifications->updateChannelNotification(
|
||||||
this->split_->getChannel()->getName(), Platform::Twitch);
|
this->split_->getChannel()->getName(), Platform::Twitch);
|
||||||
});
|
});
|
||||||
|
|
||||||
moreMenu->addAction(action);
|
moreMenu->addAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||||
{
|
{
|
||||||
auto action = new QAction(this);
|
auto action = new QAction(this);
|
||||||
action->setText("Mute highlight sound");
|
action->setText("Mute highlight sound");
|
||||||
|
@ -332,11 +343,16 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
moreMenu->addSeparator();
|
moreMenu->addSeparator();
|
||||||
moreMenu->addAction("Reconnect", this, SLOT(reconnect()));
|
if (this->split_->getChannel()->canReconnect())
|
||||||
moreMenu->addAction("Reload channel emotes", this,
|
moreMenu->addAction("Reconnect", this, SLOT(reconnect()));
|
||||||
SLOT(reloadChannelEmotes()));
|
|
||||||
moreMenu->addAction("Reload subscriber emotes", this,
|
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||||
SLOT(reloadSubscriberEmotes()));
|
{
|
||||||
|
moreMenu->addAction("Reload channel emotes", this,
|
||||||
|
SLOT(reloadChannelEmotes()));
|
||||||
|
moreMenu->addAction("Reload subscriber emotes", this,
|
||||||
|
SLOT(reloadSubscriberEmotes()));
|
||||||
|
}
|
||||||
moreMenu->addSeparator();
|
moreMenu->addSeparator();
|
||||||
moreMenu->addAction("Clear messages", this->split_, &Split::clear);
|
moreMenu->addAction("Clear messages", this->split_, &Split::clear);
|
||||||
// moreMenu->addSeparator();
|
// moreMenu->addSeparator();
|
||||||
|
@ -725,7 +741,7 @@ void SplitHeader::reloadSubscriberEmotes()
|
||||||
|
|
||||||
void SplitHeader::reconnect()
|
void SplitHeader::reconnect()
|
||||||
{
|
{
|
||||||
getApp()->twitch.server->connect();
|
this->split_->getChannel()->reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "messages/Link.hpp"
|
#include "messages/Link.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchServer.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"
|
||||||
|
|
Loading…
Reference in a new issue