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"]
|
||||
path = lib/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson
|
||||
[submodule "lib/qtkeychain"]
|
||||
path = lib/qtkeychain
|
||||
url = https://github.com/Chatterino/qtkeychain
|
||||
[submodule "lib/websocketpp"]
|
||||
path = lib/websocketpp
|
||||
url = https://github.com/ziocleto/websocketpp
|
||||
|
|
|
@ -40,8 +40,6 @@ macx {
|
|||
}
|
||||
|
||||
# Submodules
|
||||
DEFINES += IRC_NAMESPACE=Communi
|
||||
|
||||
include(lib/warnings.pri)
|
||||
include(lib/appbase.pri)
|
||||
include(lib/fmt.pri)
|
||||
|
@ -54,6 +52,7 @@ include(lib/settings.pri)
|
|||
include(lib/serialize.pri)
|
||||
include(lib/winsdk.pri)
|
||||
include(lib/rapidjson.pri)
|
||||
include(lib/qtkeychain.pri)
|
||||
|
||||
exists( $$OUT_PWD/conanbuildinfo.pri ) {
|
||||
message("Using conan packages")
|
||||
|
@ -78,7 +77,9 @@ SOURCES += \
|
|||
src/autogenerated/ResourcesAutogen.cpp \
|
||||
src/BrowserExtension.cpp \
|
||||
src/common/Channel.cpp \
|
||||
src/common/ChannelChatters.cpp \
|
||||
src/common/CompletionModel.cpp \
|
||||
src/common/Credentials.cpp \
|
||||
src/common/DownloadManager.cpp \
|
||||
src/common/Env.cpp \
|
||||
src/common/LinkParser.cpp \
|
||||
|
@ -132,8 +133,10 @@ SOURCES += \
|
|||
src/providers/emoji/Emojis.cpp \
|
||||
src/providers/ffz/FfzEmotes.cpp \
|
||||
src/providers/irc/AbstractIrcServer.cpp \
|
||||
src/providers/irc/Irc2.cpp \
|
||||
src/providers/irc/IrcAccount.cpp \
|
||||
src/providers/irc/IrcChannel2.cpp \
|
||||
src/providers/irc/IrcCommands.cpp \
|
||||
src/providers/irc/IrcConnection2.cpp \
|
||||
src/providers/irc/IrcServer.cpp \
|
||||
src/providers/LinkResolver.cpp \
|
||||
|
@ -150,9 +153,9 @@ SOURCES += \
|
|||
src/providers/twitch/TwitchChannel.cpp \
|
||||
src/providers/twitch/TwitchEmotes.cpp \
|
||||
src/providers/twitch/TwitchHelpers.cpp \
|
||||
src/providers/twitch/TwitchIrcServer.cpp \
|
||||
src/providers/twitch/TwitchMessageBuilder.cpp \
|
||||
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
||||
src/providers/twitch/TwitchServer.cpp \
|
||||
src/providers/twitch/TwitchUser.cpp \
|
||||
src/RunGui.cpp \
|
||||
src/singletons/Badges.cpp \
|
||||
|
@ -180,6 +183,7 @@ SOURCES += \
|
|||
src/widgets/AccountSwitchWidget.cpp \
|
||||
src/widgets/AttachedWindow.cpp \
|
||||
src/widgets/dialogs/EmotePopup.cpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||
src/widgets/dialogs/LoginDialog.cpp \
|
||||
src/widgets/dialogs/LogsPopup.cpp \
|
||||
|
@ -230,9 +234,11 @@ HEADERS += \
|
|||
src/common/Aliases.hpp \
|
||||
src/common/Atomic.hpp \
|
||||
src/common/Channel.hpp \
|
||||
src/common/ChannelChatters.hpp \
|
||||
src/common/Common.hpp \
|
||||
src/common/CompletionModel.hpp \
|
||||
src/common/ConcurrentMap.hpp \
|
||||
src/common/Credentials.hpp \
|
||||
src/common/DownloadManager.hpp \
|
||||
src/common/Env.hpp \
|
||||
src/common/LinkParser.hpp \
|
||||
|
@ -302,8 +308,10 @@ HEADERS += \
|
|||
src/providers/emoji/Emojis.hpp \
|
||||
src/providers/ffz/FfzEmotes.hpp \
|
||||
src/providers/irc/AbstractIrcServer.hpp \
|
||||
src/providers/irc/Irc2.hpp \
|
||||
src/providers/irc/IrcAccount.hpp \
|
||||
src/providers/irc/IrcChannel2.hpp \
|
||||
src/providers/irc/IrcCommands.hpp \
|
||||
src/providers/irc/IrcConnection2.hpp \
|
||||
src/providers/irc/IrcServer.hpp \
|
||||
src/providers/LinkResolver.hpp \
|
||||
|
@ -322,9 +330,9 @@ HEADERS += \
|
|||
src/providers/twitch/TwitchCommon.hpp \
|
||||
src/providers/twitch/TwitchEmotes.hpp \
|
||||
src/providers/twitch/TwitchHelpers.hpp \
|
||||
src/providers/twitch/TwitchIrcServer.hpp \
|
||||
src/providers/twitch/TwitchMessageBuilder.hpp \
|
||||
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
||||
src/providers/twitch/TwitchServer.hpp \
|
||||
src/providers/twitch/TwitchUser.hpp \
|
||||
src/RunGui.hpp \
|
||||
src/singletons/Badges.hpp \
|
||||
|
@ -350,6 +358,7 @@ HEADERS += \
|
|||
src/util/IsBigEndian.hpp \
|
||||
src/util/JsonQuery.hpp \
|
||||
src/util/LayoutCreator.hpp \
|
||||
src/util/Overloaded.hpp \
|
||||
src/util/QObjectRef.hpp \
|
||||
src/util/QStringHash.hpp \
|
||||
src/util/rangealgorithm.hpp \
|
||||
|
@ -363,6 +372,7 @@ HEADERS += \
|
|||
src/widgets/AccountSwitchWidget.hpp \
|
||||
src/widgets/AttachedWindow.hpp \
|
||||
src/widgets/dialogs/EmotePopup.hpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||
src/widgets/dialogs/LoginDialog.hpp \
|
||||
src/widgets/dialogs/LogsPopup.hpp \
|
||||
|
@ -414,7 +424,8 @@ RESOURCES += \
|
|||
|
||||
DISTFILES +=
|
||||
|
||||
FORMS +=
|
||||
FORMS += \
|
||||
src/widgets/dialogs/IrcConnectionEditor.ui
|
||||
|
||||
# do not use windows min/max macros
|
||||
#win32 {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
DEFINES += IRC_NAMESPACE=Communi
|
||||
|
||||
include(../lib/libcommuni/src/core/core.pri)
|
||||
include(../lib/libcommuni/src/model/model.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_signals.txt</file>
|
||||
<file>licenses/qt_lgpl-3.0.txt</file>
|
||||
<file>licenses/qtkeychain.txt</file>
|
||||
<file>licenses/rapidjson.txt</file>
|
||||
<file>licenses/websocketpp.txt</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/chatterino/ChatterinoBadges.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/irc/Irc2.hpp"
|
||||
#include "providers/twitch/PubsubClient.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "singletons/Logging.hpp"
|
||||
|
@ -59,7 +60,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||
, ignores(&this->emplace<IgnoreController>())
|
||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
||||
, moderationActions(&this->emplace<ModerationActions>())
|
||||
, twitch2(&this->emplace<TwitchServer>())
|
||||
, twitch2(&this->emplace<TwitchIrcServer>())
|
||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||
, logging(&this->emplace<Logging>())
|
||||
|
||||
|
@ -78,6 +79,8 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||
assert(isAppInitialized == false);
|
||||
isAppInitialized = true;
|
||||
|
||||
Irc::getInstance().load();
|
||||
|
||||
for (auto &singleton : this->singletons_)
|
||||
{
|
||||
singleton->initialize(settings, paths);
|
||||
|
@ -116,6 +119,8 @@ void Application::save()
|
|||
|
||||
void Application::initNm(Paths &paths)
|
||||
{
|
||||
(void)paths;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# if defined QT_NO_DEBUG || defined C_DEBUG_NM
|
||||
registerNmHost(paths);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchServer;
|
||||
class TwitchIrcServer;
|
||||
class PubSub;
|
||||
|
||||
class CommandController;
|
||||
|
@ -67,14 +67,14 @@ public:
|
|||
IgnoreController *const ignores{};
|
||||
TaggedUsersController *const taggedUsers{};
|
||||
ModerationActions *const moderationActions{};
|
||||
TwitchServer *const twitch2{};
|
||||
TwitchIrcServer *const twitch2{};
|
||||
ChatterinoBadges *const chatterinoBadges{};
|
||||
|
||||
/*[[deprecated]]*/ Logging *const logging{};
|
||||
|
||||
/// Provider-specific
|
||||
struct {
|
||||
/*[[deprecated("use twitch2 instead")]]*/ TwitchServer *server{};
|
||||
/*[[deprecated("use twitch2 instead")]]*/ TwitchIrcServer *server{};
|
||||
/*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{};
|
||||
} twitch;
|
||||
|
||||
|
|
|
@ -70,13 +70,6 @@ void Channel::addMessage(MessagePtr message,
|
|||
auto app = getApp();
|
||||
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
|
||||
if (this->isTwitchChannel() &&
|
||||
(!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
|
||||
|
@ -246,10 +239,6 @@ void Channel::deleteMessage(QString messageID)
|
|||
}
|
||||
}
|
||||
|
||||
void Channel::addRecentChatter(const MessagePtr &message)
|
||||
{
|
||||
}
|
||||
|
||||
bool Channel::canSendMessage() const
|
||||
{
|
||||
return false;
|
||||
|
@ -291,6 +280,15 @@ bool Channel::shouldIgnoreHighlights() const
|
|||
this->type_ == Type::TwitchWhispers;
|
||||
}
|
||||
|
||||
bool Channel::canReconnect() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Channel::reconnect()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> Channel::getEmpty()
|
||||
{
|
||||
static std::shared_ptr<Channel> channel(new Channel("", Type::None));
|
||||
|
|
|
@ -37,15 +37,16 @@ public:
|
|||
TwitchWatching,
|
||||
TwitchMentions,
|
||||
TwitchEnd,
|
||||
Irc,
|
||||
Misc
|
||||
};
|
||||
|
||||
explicit Channel(const QString &name, Type type);
|
||||
virtual ~Channel();
|
||||
|
||||
// SIGNALS
|
||||
pajlada::Signals::Signal<const QString &, const QString &, bool &>
|
||||
sendMessageSignal;
|
||||
|
||||
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
|
||||
pajlada::Signals::Signal<MessagePtr &, boost::optional<MessageFlags>>
|
||||
messageAppended;
|
||||
|
@ -60,6 +61,7 @@ public:
|
|||
virtual bool isEmpty() const;
|
||||
LimitedQueueSnapshot<MessagePtr> getMessageSnapshot();
|
||||
|
||||
// MESSAGES
|
||||
// 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
|
||||
// type of split
|
||||
|
@ -71,9 +73,11 @@ public:
|
|||
void disableAllMessages();
|
||||
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
||||
void deleteMessage(QString messageID);
|
||||
void clearMessages();
|
||||
|
||||
QStringList modList;
|
||||
|
||||
// CHANNEL INFO
|
||||
virtual bool canSendMessage() const;
|
||||
virtual void sendMessage(const QString &message);
|
||||
virtual bool isMod() const;
|
||||
|
@ -82,6 +86,8 @@ public:
|
|||
virtual bool hasHighRateLimit() const;
|
||||
virtual bool isLive() const;
|
||||
virtual bool shouldIgnoreHighlights() const;
|
||||
virtual bool canReconnect() const;
|
||||
virtual void reconnect();
|
||||
|
||||
static std::shared_ptr<Channel> getEmpty();
|
||||
|
||||
|
@ -89,7 +95,6 @@ public:
|
|||
|
||||
protected:
|
||||
virtual void onConnected();
|
||||
virtual void addRecentChatter(const MessagePtr &message);
|
||||
|
||||
private:
|
||||
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/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.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
|
||||
{
|
||||
(void)parent;
|
||||
|
||||
return this->rows_.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
(void)parent;
|
||||
|
||||
return this->columnCount_;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
||||
column >= this->columnCount_)
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return rows_[row].items[column]->data(role);
|
||||
}
|
||||
|
@ -123,8 +130,11 @@ public:
|
|||
int role) override
|
||||
{
|
||||
int row = index.row(), column = index.column();
|
||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||
column < this->columnCount_);
|
||||
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
||||
column >= this->columnCount_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Row &rowItem = this->rows_[row];
|
||||
|
||||
|
@ -185,6 +195,13 @@ public:
|
|||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
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 &&
|
||||
column < this->columnCount_);
|
||||
|
||||
|
@ -207,6 +224,8 @@ public:
|
|||
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||
{
|
||||
(void)parent;
|
||||
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
|
@ -237,18 +256,22 @@ protected:
|
|||
std::vector<QStandardItem *> &row,
|
||||
int proposedIndex)
|
||||
{
|
||||
(void)item, (void)row;
|
||||
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
virtual void afterRemoved(const TVectorItem &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
(void)item, (void)row, (void)index;
|
||||
}
|
||||
|
||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
int column, const QVariant &value, int role,
|
||||
int rowIndex)
|
||||
{
|
||||
(void)row, (void)column, (void)value, (void)role, (void)rowIndex;
|
||||
}
|
||||
|
||||
void insertCustomRow(std::vector<QStandardItem *> row, int index)
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/twitch/TwitchApi.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "controllers/notifications/NotificationModel.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchApi.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
|
|
@ -32,6 +32,7 @@ enum class MessageFlag : uint32_t {
|
|||
RecentMessage = (1 << 15),
|
||||
Whisper = (1 << 16),
|
||||
HighlightedWhisper = (1 << 17),
|
||||
Debug = (1 << 18),
|
||||
};
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
|
|
|
@ -269,6 +269,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
|||
{
|
||||
backgroundColor = QColor("#404040");
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::Debug))
|
||||
{
|
||||
backgroundColor = QColor("#4A273D");
|
||||
}
|
||||
|
||||
painter.fillRect(buffer->rect(), backgroundColor);
|
||||
|
||||
|
|
|
@ -18,13 +18,17 @@ const int MAX_FALLOFF_COUNTER = 60;
|
|||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
// XXX: don't create write connection if there is not separate write connection.
|
||||
this->writeConnection_.reset(new IrcConnection);
|
||||
this->writeConnection_->moveToThread(
|
||||
QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(
|
||||
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
|
||||
this->readConnection_.reset(new IrcConnection);
|
||||
|
@ -32,21 +36,18 @@ AbstractIrcServer::AbstractIrcServer()
|
|||
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||
this, [this](auto msg) { this->readConnectionMessageReceived(msg); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::privateMessageReceived,
|
||||
&Communi::IrcConnection::privateMessageReceived, this,
|
||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||
QObject::connect(
|
||||
this->readConnection_.get(), &Communi::IrcConnection::connected,
|
||||
this->readConnection_.get(), &Communi::IrcConnection::connected, this,
|
||||
[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(),
|
||||
&Communi::IrcConnection::disconnected,
|
||||
&Communi::IrcConnection::disconnected, this,
|
||||
[this] { this->onDisconnected(); });
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::socketError,
|
||||
&Communi::IrcConnection::socketError, this,
|
||||
[this] { this->onSocketError(); });
|
||||
|
||||
// listen to reconnect request
|
||||
|
@ -75,37 +76,29 @@ void AbstractIrcServer::connect()
|
|||
{
|
||||
this->disconnect();
|
||||
|
||||
bool separateWriteConnection = this->hasSeparateWriteConnection();
|
||||
|
||||
if (separateWriteConnection)
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->initializeConnection(this->writeConnection_.get(), false, true);
|
||||
this->initializeConnection(this->readConnection_.get(), true, false);
|
||||
this->initializeConnection(this->writeConnection_.get(), Write);
|
||||
this->initializeConnection(this->readConnection_.get(), Read);
|
||||
}
|
||||
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();
|
||||
}
|
||||
if (type & Read)
|
||||
{
|
||||
this->readConnection_->open();
|
||||
}
|
||||
|
||||
// this->onConnected();
|
||||
// possbile event: started to connect
|
||||
}
|
||||
|
||||
void AbstractIrcServer::disconnect()
|
||||
|
@ -113,7 +106,10 @@ void AbstractIrcServer::disconnect()
|
|||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
this->readConnection_->close();
|
||||
this->writeConnection_->close();
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->close();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||
|
@ -139,10 +135,10 @@ void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
|||
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
(void)message;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||
const QString &dirtyChannelName)
|
||||
ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
|
@ -162,26 +158,24 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
|||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
QString clojuresInCppAreShit = channelName;
|
||||
|
||||
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
|
||||
|
||||
log("[AbstractIrcServer::addChannel] {} was destroyed",
|
||||
clojuresInCppAreShit);
|
||||
this->channels.remove(clojuresInCppAreShit);
|
||||
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
|
||||
this->channels.remove(channelName);
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -189,20 +183,25 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||
const QString &dirtyChannelName)
|
||||
ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
|
@ -230,10 +229,35 @@ std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
|||
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)
|
||||
{
|
||||
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");
|
||||
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||
auto reconnected = makeSystemMessage("reconnected");
|
||||
|
@ -267,6 +291,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
|||
|
||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
(void)connection;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
|
@ -297,12 +322,16 @@ void AbstractIrcServer::onSocketError()
|
|||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
(void)channelName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
return dirtyChannelName;
|
||||
if (dirtyChannelName.startsWith('#'))
|
||||
return dirtyChannelName.mid(1);
|
||||
else
|
||||
return dirtyChannelName;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
|
@ -324,6 +353,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
|
|||
void AbstractIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
(void)message;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::readConnectionMessageReceived(
|
||||
|
@ -337,7 +367,7 @@ void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
|||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
ChannelPtr chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <IrcMessage>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
@ -13,9 +14,11 @@ namespace chatterino {
|
|||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
|
||||
class AbstractIrcServer
|
||||
class AbstractIrcServer : public QObject
|
||||
{
|
||||
public:
|
||||
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
|
||||
|
||||
virtual ~AbstractIrcServer() = default;
|
||||
|
||||
// connection
|
||||
|
@ -26,14 +29,13 @@ public:
|
|||
void sendRawMessage(const QString &rawMessage);
|
||||
|
||||
// channels
|
||||
std::shared_ptr<Channel> getOrAddChannel(const QString &dirtyChannelName);
|
||||
std::shared_ptr<Channel> getChannelOrEmpty(const QString &dirtyChannelName);
|
||||
ChannelPtr getOrAddChannel(const QString &dirtyChannelName);
|
||||
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName);
|
||||
std::vector<std::weak_ptr<Channel>> getChannels();
|
||||
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal connected;
|
||||
pajlada::Signals::NoArgSignal disconnected;
|
||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
|
||||
// onPrivateMessage;
|
||||
|
||||
void addFakeMessage(const QString &data);
|
||||
|
||||
|
@ -43,8 +45,8 @@ public:
|
|||
protected:
|
||||
AbstractIrcServer();
|
||||
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) = 0;
|
||||
virtual void initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type) = 0;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) = 0;
|
||||
|
||||
|
@ -63,14 +65,23 @@ protected:
|
|||
virtual bool hasSeparateWriteConnection() const = 0;
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||
|
||||
void open(ConnectionType type);
|
||||
|
||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||
std::mutex channelMutex;
|
||||
|
||||
private:
|
||||
void initConnection();
|
||||
|
||||
std::unique_ptr<IrcConnection> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection> readConnection_ = nullptr;
|
||||
struct Deleter {
|
||||
void operator()(IrcConnection *conn)
|
||||
{
|
||||
conn->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
|
||||
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
@ -78,6 +89,7 @@ private:
|
|||
std::mutex connectionMutex_;
|
||||
|
||||
// bool autoReconnect_ = false;
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
};
|
||||
|
||||
} // 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 "debug/AssertInGuiThread.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/irc/IrcCommands.hpp"
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
|
||||
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
|
||||
|
|
|
@ -1,11 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/ChannelChatters.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcChannel
|
||||
//{
|
||||
// public:
|
||||
// IrcChannel();
|
||||
//};
|
||||
//
|
||||
class Irc;
|
||||
class IrcServer;
|
||||
|
||||
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
|
||||
|
|
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())
|
||||
{
|
||||
this->sendRaw("PING");
|
||||
this->sendRaw("PING chatterino/ping");
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
this->recentlyReceivedMessage_ = false;
|
||||
|
|
|
@ -1,12 +1,260 @@
|
|||
#include "IrcServer.hpp"
|
||||
|
||||
#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 {
|
||||
|
||||
// IrcServer::IrcServer(const QString &hostname, int port)
|
||||
//{
|
||||
// this->initConnection();
|
||||
//}
|
||||
//
|
||||
IrcServer::IrcServer(const IrcServerData &data)
|
||||
: data_(new IrcServerData(data))
|
||||
{
|
||||
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
|
||||
|
|
|
@ -5,20 +5,34 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
// class IrcServer
|
||||
//{
|
||||
// public:
|
||||
// IrcServer(const QString &hostname, int port);
|
||||
struct IrcServerData;
|
||||
|
||||
// void setAccount(std::shared_ptr<IrcAccount> newAccount);
|
||||
// std::shared_ptr<IrcAccount> getAccount() const;
|
||||
class IrcServer : public AbstractIrcServer
|
||||
{
|
||||
public:
|
||||
explicit IrcServer(const IrcServerData &data);
|
||||
IrcServer(const IrcServerData &data,
|
||||
const std::vector<std::weak_ptr<Channel>> &restoreChannels);
|
||||
~IrcServer() override;
|
||||
|
||||
// protected:
|
||||
// virtual void initializeConnection(Communi::IrcConnection *connection, bool
|
||||
// isReadConnection);
|
||||
int id();
|
||||
const QString &user();
|
||||
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
|
||||
|
|
|
@ -20,7 +20,7 @@ protected:
|
|||
QString chatroomOwnerId;
|
||||
QString chatroomOwnerName;
|
||||
|
||||
friend class TwitchServer;
|
||||
friend class TwitchIrcServer;
|
||||
friend class TwitchMessageBuilder;
|
||||
friend class IrcMessageHandler;
|
||||
};
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
#include "IrcMessageHandler.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/LimitedQueue.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchHelpers.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
|
@ -85,7 +87,7 @@ std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
|
|||
}
|
||||
|
||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||
TwitchServer &server)
|
||||
TwitchIrcServer &server)
|
||||
{
|
||||
this->addMessage(message, message->target(), message->content(), server,
|
||||
false, message->isAction());
|
||||
|
@ -93,8 +95,9 @@ void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
|||
|
||||
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||
const QString &target,
|
||||
const QString &content, TwitchServer &server,
|
||||
bool isSub, bool isAction)
|
||||
const QString &content,
|
||||
TwitchIrcServer &server, bool isSub,
|
||||
bool isAction)
|
||||
{
|
||||
QString channelName;
|
||||
if (!trimChannelName(target, channelName))
|
||||
|
@ -144,6 +147,10 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
|||
}
|
||||
|
||||
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,
|
||||
TwitchServer &server)
|
||||
TwitchIrcServer &server)
|
||||
{
|
||||
auto data = message->toData();
|
||||
|
||||
|
@ -591,7 +598,12 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
|||
if (TwitchChannel *twitchChannel =
|
||||
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 =
|
||||
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 {
|
||||
|
||||
class TwitchServer;
|
||||
class TwitchIrcServer;
|
||||
class Channel;
|
||||
|
||||
class IrcMessageHandler
|
||||
|
@ -23,7 +23,7 @@ public:
|
|||
std::vector<MessagePtr> parsePrivMessage(
|
||||
Channel *channel, Communi::IrcPrivateMessage *message);
|
||||
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||
TwitchServer &server);
|
||||
TwitchIrcServer &server);
|
||||
|
||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||
|
@ -36,7 +36,7 @@ public:
|
|||
std::vector<MessagePtr> parseUserNoticeMessage(
|
||||
Channel *channel, Communi::IrcMessage *message);
|
||||
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||
TwitchServer &server);
|
||||
TwitchIrcServer &server);
|
||||
|
||||
void handleModeMessage(Communi::IrcMessage *message);
|
||||
|
||||
|
@ -51,8 +51,8 @@ public:
|
|||
|
||||
private:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchServer &server, bool isResub,
|
||||
bool isAction);
|
||||
const QString &content, TwitchIrcServer &server,
|
||||
bool isResub, bool isAction);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "providers/twitch/PubsubActions.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <QString>
|
||||
|
|
|
@ -79,6 +79,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
TwitchBadges &globalTwitchBadges, BttvEmotes &bttv,
|
||||
FfzEmotes &ffz)
|
||||
: Channel(name, Channel::Type::Twitch)
|
||||
, ChannelChatters(*static_cast<Channel *>(this))
|
||||
, subscriptionUrl_("https://www.twitch.tv/subs/" + name)
|
||||
, channelUrl_("https://twitch.tv/" + name)
|
||||
, popoutPlayerUrl_("https://player.twitch.tv/?channel=" + name)
|
||||
|
@ -280,69 +281,14 @@ bool TwitchChannel::hasHighRateLimit() const
|
|||
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();
|
||||
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;
|
||||
});
|
||||
}
|
||||
getApp()->twitch.server->connect();
|
||||
}
|
||||
|
||||
QString TwitchChannel::roomId() const
|
||||
|
@ -384,11 +330,6 @@ AccessGuard<const TwitchChannel::StreamStatus>
|
|||
return this->streamStatus_.accessConst();
|
||||
}
|
||||
|
||||
AccessGuard<const UsernameSet> TwitchChannel::accessChatters() const
|
||||
{
|
||||
return this->chatters_.accessConst();
|
||||
}
|
||||
|
||||
const TwitchBadges &TwitchChannel::globalTwitchBadges() const
|
||||
{
|
||||
return this->globalTwitchBadges_;
|
||||
|
@ -691,7 +632,7 @@ void TwitchChannel::refreshChatters()
|
|||
auto pair = parseChatters(result.parseJson());
|
||||
if (pair.first)
|
||||
{
|
||||
*this->chatters_.access() = std::move(pair.second);
|
||||
this->setChatters(std::move(pair.second));
|
||||
}
|
||||
|
||||
return pair.first;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "common/Aliases.hpp"
|
||||
#include "common/Atomic.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/ChannelChatters.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
|
@ -29,9 +30,11 @@ class TwitchBadges;
|
|||
class FfzEmotes;
|
||||
class BttvEmotes;
|
||||
|
||||
class TwitchServer;
|
||||
class TwitchIrcServer;
|
||||
|
||||
class TwitchChannel : public Channel, pajlada::Signals::SignalHolder
|
||||
class TwitchChannel : public Channel,
|
||||
public ChannelChatters,
|
||||
pajlada::Signals::SignalHolder
|
||||
{
|
||||
public:
|
||||
struct StreamStatus {
|
||||
|
@ -64,6 +67,8 @@ public:
|
|||
bool isStaff() const;
|
||||
virtual bool isBroadcaster() const override;
|
||||
virtual bool hasHighRateLimit() const override;
|
||||
virtual bool canReconnect() const override;
|
||||
virtual void reconnect() override;
|
||||
|
||||
// Data
|
||||
const QString &subscriptionUrl();
|
||||
|
@ -73,7 +78,6 @@ public:
|
|||
QString roomId() const;
|
||||
AccessGuard<const RoomModes> accessRoomModes() const;
|
||||
AccessGuard<const StreamStatus> accessStreamStatus() const;
|
||||
AccessGuard<const UsernameSet> accessChatters() const;
|
||||
|
||||
// Emotes
|
||||
const TwitchBadges &globalTwitchBadges() const;
|
||||
|
@ -101,9 +105,6 @@ public:
|
|||
pajlada::Signals::NoArgSignal liveStatusChanged;
|
||||
pajlada::Signals::NoArgSignal roomModesChanged;
|
||||
|
||||
protected:
|
||||
void addRecentChatter(const MessagePtr &message) override;
|
||||
|
||||
private:
|
||||
struct NameOptions {
|
||||
QString displayName;
|
||||
|
@ -125,8 +126,6 @@ private:
|
|||
void refreshCheerEmotes();
|
||||
void loadRecentMessages();
|
||||
|
||||
void addJoinedUser(const QString &user);
|
||||
void addPartedUser(const QString &user);
|
||||
void setLive(bool newLiveStatus);
|
||||
void setMod(bool value);
|
||||
void setVIP(bool value);
|
||||
|
@ -140,7 +139,6 @@ private:
|
|||
const QString popoutPlayerUrl_;
|
||||
UniqueAccess<StreamStatus> streamStatus_;
|
||||
UniqueAccess<RoomModes> roomModes_;
|
||||
UniqueAccess<UsernameSet> chatters_; // maps 2 char prefix to set of names
|
||||
|
||||
// Emotes
|
||||
TwitchBadges &globalTwitchBadges_;
|
||||
|
@ -163,18 +161,13 @@ private:
|
|||
bool staff_ = false;
|
||||
UniqueAccess<QString> roomID_;
|
||||
|
||||
UniqueAccess<QStringList> joinedUsers_;
|
||||
bool joinedUsersMergeQueued_ = false;
|
||||
UniqueAccess<QStringList> partedUsers_;
|
||||
bool partedUsersMergeQueued_ = false;
|
||||
|
||||
// --
|
||||
QString lastSentMessage_;
|
||||
QObject lifetimeGuard_;
|
||||
QTimer liveStatusTimer_;
|
||||
QTimer chattersListTimer_;
|
||||
|
||||
friend class TwitchServer;
|
||||
friend class TwitchIrcServer;
|
||||
friend class TwitchMessageBuilder;
|
||||
friend class IrcMessageHandler;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "TwitchServer.hpp"
|
||||
#include "TwitchIrcServer.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
|
@ -38,13 +38,11 @@ namespace {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
TwitchServer::TwitchServer()
|
||||
TwitchIrcServer::TwitchIrcServer()
|
||||
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
||||
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||
{
|
||||
qDebug() << "init TwitchServer";
|
||||
|
||||
this->pubsub = new PubSub;
|
||||
|
||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
||||
|
@ -53,7 +51,7 @@ TwitchServer::TwitchServer()
|
|||
// false);
|
||||
}
|
||||
|
||||
void TwitchServer::initialize(Settings &settings, Paths &paths)
|
||||
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
|
||||
{
|
||||
getApp()->accounts->twitch.currentUserChanged.connect(
|
||||
[this]() { postToThread([this] { this->connect(); }); });
|
||||
|
@ -63,11 +61,9 @@ void TwitchServer::initialize(Settings &settings, Paths &paths)
|
|||
this->ffz.loadEmotes();
|
||||
}
|
||||
|
||||
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite)
|
||||
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type)
|
||||
{
|
||||
this->singleConnection_ = isRead == isWrite;
|
||||
|
||||
std::shared_ptr<TwitchAccount> account =
|
||||
getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
|
@ -97,9 +93,12 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
|||
// SSL enabled: irc://irc.chat.twitch.tv:6697
|
||||
connection->setHost("irc.chat.twitch.tv");
|
||||
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;
|
||||
if (isChatroom(channelName))
|
||||
|
@ -123,13 +122,17 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
|||
return std::shared_ptr<Channel>(channel);
|
||||
}
|
||||
|
||||
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
void TwitchIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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();
|
||||
|
||||
|
@ -193,7 +197,7 @@ void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
|||
}
|
||||
}
|
||||
|
||||
void TwitchServer::onReadConnected(IrcConnection *connection)
|
||||
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onReadConnected(connection);
|
||||
|
||||
|
@ -202,7 +206,7 @@ void TwitchServer::onReadConnected(IrcConnection *connection)
|
|||
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
|
||||
}
|
||||
|
||||
void TwitchServer::onWriteConnected(IrcConnection *connection)
|
||||
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
AbstractIrcServer::onWriteConnected(connection);
|
||||
|
||||
|
@ -211,7 +215,7 @@ void TwitchServer::onWriteConnected(IrcConnection *connection)
|
|||
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)
|
||||
{
|
||||
if (channelName == "/whispers")
|
||||
|
@ -249,7 +253,7 @@ std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void TwitchServer::forEachChannelAndSpecialChannels(
|
||||
void TwitchIrcServer::forEachChannelAndSpecialChannels(
|
||||
std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
this->forEachChannel(func);
|
||||
|
@ -258,7 +262,7 @@ void TwitchServer::forEachChannelAndSpecialChannels(
|
|||
func(this->mentionsChannel);
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
|
||||
const QString &channelId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
@ -283,19 +287,22 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
|||
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 getSettings()->twitchSeperateWriteConnection;
|
||||
}
|
||||
|
||||
void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
const QString &message, bool &sent)
|
||||
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
|
||||
const QString &message, bool &sent)
|
||||
{
|
||||
sent = false;
|
||||
|
||||
|
@ -354,11 +361,11 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
|||
sent = true;
|
||||
}
|
||||
|
||||
const BttvEmotes &TwitchServer::getBttvEmotes() const
|
||||
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
|
||||
{
|
||||
return this->bttv;
|
||||
}
|
||||
const FfzEmotes &TwitchServer::getFfzEmotes() const
|
||||
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
|
||||
{
|
||||
return this->ffz;
|
||||
}
|
|
@ -20,11 +20,11 @@ class Paths;
|
|||
class PubSub;
|
||||
class TwitchChannel;
|
||||
|
||||
class TwitchServer final : public AbstractIrcServer, public Singleton
|
||||
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
|
||||
{
|
||||
public:
|
||||
TwitchServer();
|
||||
virtual ~TwitchServer() override = default;
|
||||
TwitchIrcServer();
|
||||
virtual ~TwitchIrcServer() override = default;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||
|
||||
|
@ -44,8 +44,8 @@ public:
|
|||
const FfzEmotes &getFfzEmotes() const;
|
||||
|
||||
protected:
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||
bool isWrite) override;
|
||||
virtual void initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type) override;
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) override;
|
||||
|
||||
|
@ -75,7 +75,6 @@ private:
|
|||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||
|
||||
bool singleConnection_ = false;
|
||||
TwitchBadges twitchBadges;
|
||||
BttvEmotes bttv;
|
||||
FfzEmotes ffz;
|
|
@ -10,7 +10,7 @@
|
|||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ void Paths::initSubDirectories()
|
|||
this->messageLogDirectory = makePath("Logs");
|
||||
this->miscDirectory = makePath("Misc");
|
||||
this->twitchProfileAvatars = makePath("ProfileAvatars");
|
||||
QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
||||
//QDir().mkdir(this->twitchProfileAvatars + "/twitch");
|
||||
}
|
||||
|
||||
Paths *getPaths()
|
||||
|
|
|
@ -199,6 +199,10 @@ public:
|
|||
|
||||
/// Misc
|
||||
BoolSetting betaUpdates = {"/misc/beta", false};
|
||||
#ifdef Q_OS_LINUX
|
||||
BoolSetting useKeyring = {"/misc/useKeyring", true};
|
||||
#endif
|
||||
|
||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||
|
@ -208,6 +212,15 @@ public:
|
|||
|
||||
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:
|
||||
void updateModerationActions();
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/StreamLink.hpp"
|
||||
#include "widgets/helper/CommonTexts.hpp"
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "debug/Log.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/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
@ -611,6 +614,20 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
|
|||
obj.insert("type", "whispers");
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (type == "irc")
|
||||
{
|
||||
return Irc::getInstance().getOrAddChannel(
|
||||
obj.value("server").toInt(-1), obj.value("channel").toString());
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
|
|
@ -126,6 +126,20 @@ public:
|
|||
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:
|
||||
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
|
||||
|
|
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)
|
||||
{
|
||||
this->conn_ =
|
||||
QObject::connect(other, &QObject::destroyed,
|
||||
[this](QObject *) { this->set(nullptr); });
|
||||
QObject::connect(other, &QObject::destroyed, qApp,
|
||||
[this](QObject *) { this->set(nullptr); },
|
||||
Qt::DirectConnection);
|
||||
}
|
||||
|
||||
this->t_ = other;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace chatterino {
|
|||
static void setBoolItem(QStandardItem *item, bool value,
|
||||
bool userCheckable = true, bool selectable = true)
|
||||
{
|
||||
item->setFlags((Qt::ItemFlags)(
|
||||
item->setFlags(Qt::ItemFlags(
|
||||
Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) |
|
||||
(userCheckable ? Qt::ItemIsUserCheckable : 0)));
|
||||
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)
|
||||
{
|
||||
item->setData(value, Qt::EditRole);
|
||||
item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled |
|
||||
(selectable ? Qt::ItemIsSelectable : 0) |
|
||||
(editable ? (Qt::ItemIsEditable) : 0)));
|
||||
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled |
|
||||
(selectable ? Qt::ItemIsSelectable : 0) |
|
||||
(editable ? (Qt::ItemIsEditable) : 0)));
|
||||
}
|
||||
|
||||
static QStandardItem *emptyItem()
|
||||
{
|
||||
auto *item = new QStandardItem();
|
||||
item->setFlags((Qt::ItemFlags)0);
|
||||
item->setFlags(Qt::ItemFlags(0));
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#include "widgets/Window.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Credentials.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/Updates.hpp"
|
||||
|
@ -108,7 +109,7 @@ bool Window::event(QEvent *event)
|
|||
break;
|
||||
|
||||
default:;
|
||||
};
|
||||
}
|
||||
|
||||
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 "Application.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/dialogs/IrcConnectionEditor.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
|
@ -14,7 +15,12 @@
|
|||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <QTableView>
|
||||
#include "providers/irc/Irc2.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
|
||||
#define TAB_TWITCH 0
|
||||
#define TAB_IRC 1
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -122,21 +128,69 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
|||
}
|
||||
|
||||
// irc
|
||||
/*
|
||||
{
|
||||
LayoutCreator<QWidget> obj(new QWidget());
|
||||
auto outerBox = obj.setLayoutType<QFormLayout>();
|
||||
|
||||
{
|
||||
LayoutCreator<QWidget> obj(new QWidget());
|
||||
auto vbox = obj.setLayoutType<QVBoxLayout>();
|
||||
auto form = vbox.emplace<QFormLayout>();
|
||||
auto view = this->ui_.irc.servers = new EditableModelView(
|
||||
Irc::getInstance().newConnectionModel(this));
|
||||
|
||||
form->addRow(new QLabel("User name:"), new QLineEdit());
|
||||
form->addRow(new QLabel("First nick choice:"), new QLineEdit());
|
||||
form->addRow(new QLabel("Second nick choice:"), new QLineEdit());
|
||||
form->addRow(new QLabel("Third nick choice:"), new QLineEdit());
|
||||
view->setTitles({"host", "port", "ssl", "user", "nick", "real",
|
||||
"password", "login command"});
|
||||
view->getTableView()->horizontalHeader()->resizeSection(0, 140);
|
||||
|
||||
auto tab = notebook->addPage(obj.getElement());
|
||||
tab->setCustomTitle("Irc");
|
||||
view->getTableView()->horizontalHeader()->setSectionHidden(1, true);
|
||||
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);
|
||||
|
||||
|
@ -151,7 +205,7 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
|||
[=](bool) { this->close(); });
|
||||
}
|
||||
|
||||
this->setScaleIndependantSize(300, 310);
|
||||
this->setMinimumSize(300, 310);
|
||||
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
||||
this->ui_.twitch.channel->setFocus();
|
||||
|
||||
|
@ -161,10 +215,24 @@ SelectChannelDialog::SelectChannelDialog(QWidget *parent)
|
|||
auto *shortcut_cancel = new QShortcut(QKeySequence("Esc"), this);
|
||||
QObject::connect(shortcut_cancel, &QShortcut::activated,
|
||||
[=] { this->close(); });
|
||||
|
||||
// restore ui state
|
||||
this->ui_.notebook->selectIndex(getSettings()->lastSelectChannelTab);
|
||||
this->ui_.irc.servers->getTableView()->selectRow(
|
||||
getSettings()->lastSelectIrcConn);
|
||||
}
|
||||
|
||||
void SelectChannelDialog::ok()
|
||||
{
|
||||
// save ui state
|
||||
getSettings()->lastSelectChannelTab =
|
||||
this->ui_.notebook->getSelectedIndex();
|
||||
getSettings()->lastSelectIrcConn = this->ui_.irc.servers->getTableView()
|
||||
->selectionModel()
|
||||
->currentIndex()
|
||||
.row();
|
||||
|
||||
// accept and close
|
||||
this->hasSelectedChannel_ = true;
|
||||
this->close();
|
||||
}
|
||||
|
@ -204,6 +272,32 @@ void SelectChannelDialog::setSelectedChannel(IndirectChannel _channel)
|
|||
this->ui_.twitch.whispers->setFocus();
|
||||
}
|
||||
break;
|
||||
case Channel::Type::Irc:
|
||||
{
|
||||
this->ui_.notebook->selectIndex(TAB_IRC);
|
||||
this->ui_.irc.channel->setText(_channel.get()->getName());
|
||||
|
||||
if (auto ircChannel =
|
||||
dynamic_cast<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:
|
||||
{
|
||||
this->ui_.notebook->selectIndex(TAB_TWITCH);
|
||||
|
@ -245,6 +339,27 @@ IndirectChannel SelectChannelDialog::getSelectedChannel() const
|
|||
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_;
|
||||
|
@ -258,7 +373,7 @@ bool SelectChannelDialog::hasSeletedChannel() const
|
|||
bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
|
||||
QEvent *event)
|
||||
{
|
||||
auto *widget = (QWidget *)watched;
|
||||
auto *widget = static_cast<QWidget *>(watched);
|
||||
|
||||
if (event->type() == QEvent::FocusIn)
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace chatterino {
|
||||
|
||||
class Notebook;
|
||||
class EditableModelView;
|
||||
|
||||
class SelectChannelDialog final : public BaseWindow
|
||||
{
|
||||
|
@ -47,6 +48,10 @@ private:
|
|||
QRadioButton *mentions;
|
||||
QRadioButton *watching;
|
||||
} twitch;
|
||||
struct {
|
||||
QLineEdit *channel;
|
||||
EditableModelView *servers;
|
||||
} irc;
|
||||
} ui_;
|
||||
|
||||
EventFilter tabFilter_;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "messages/layouts/MessageLayout.hpp"
|
||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/TooltipPreviewImage.hpp"
|
||||
|
|
|
@ -98,6 +98,9 @@ AboutPage::AboutPage()
|
|||
addLicense(form.getElement(), "Websocketpp",
|
||||
"https://www.zaphoyd.com/websocketpp/",
|
||||
":/licenses/websocketpp.txt");
|
||||
addLicense(form.getElement(), "QtKeychain",
|
||||
"https://github.com/frankosterfeld/qtkeychain",
|
||||
":/licenses/qtkeychain.txt");
|
||||
}
|
||||
|
||||
auto attributions = layout.emplace<QGroupBox>("Attributions...");
|
||||
|
|
|
@ -383,6 +383,16 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
layout.addCheckbox("Open links in incognito/private mode",
|
||||
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,
|
||||
true);
|
||||
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",
|
||||
s.loadTwitchMessageHistoryOnConnect);
|
||||
|
||||
layout.addCheckbox("Show unhandled irc messages",
|
||||
s.showUnhandledIrcMessages);
|
||||
|
||||
layout.addTitle("Cache");
|
||||
layout.addDescription(
|
||||
"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/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/pings/PingController.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
|
@ -211,9 +211,12 @@ void SplitHeader::initializeLayout()
|
|||
}),
|
||||
// dropdown
|
||||
this->dropdownButton_ = makeWidget<Button>([&](auto w) {
|
||||
auto menu = this->createMainMenu();
|
||||
this->mainMenu_ = menu.get();
|
||||
w->setMenu(std::move(menu));
|
||||
/// XXX: this never gets disconnected
|
||||
this->split_->channelChanged.connect([this] {
|
||||
auto menu = this->createMainMenu();
|
||||
this->mainMenu_ = menu.get();
|
||||
this->dropdownButton_->setMenu(std::move(menu));
|
||||
});
|
||||
}),
|
||||
// add split
|
||||
this->addButton_ = makeWidget<Button>([&](auto w) {
|
||||
|
@ -275,13 +278,17 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
|||
});
|
||||
#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
|
||||
menu->addAction(OPEN_PLAYER_IN_BROWSER, this->split_,
|
||||
&Split::openBrowserPlayer);
|
||||
menu->addAction(OPEN_PLAYER_IN_BROWSER, this->split_,
|
||||
&Split::openBrowserPlayer);
|
||||
#endif
|
||||
menu->addAction(OPEN_IN_STREAMLINK, this->split_, &Split::openInStreamlink);
|
||||
menu->addSeparator();
|
||||
menu->addAction(OPEN_IN_STREAMLINK, this->split_,
|
||||
&Split::openInStreamlink);
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
{
|
||||
// "How to..." sub menu
|
||||
|
@ -294,26 +301,30 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
|||
// sub menu
|
||||
auto moreMenu = new QMenu("More", this);
|
||||
|
||||
moreMenu->addAction("Show viewer list", this->split_,
|
||||
&Split::showViewerList);
|
||||
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||
{
|
||||
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);
|
||||
action->setText("Notify when live");
|
||||
action->setCheckable(true);
|
||||
auto action = new QAction(this);
|
||||
action->setText("Notify when live");
|
||||
action->setCheckable(true);
|
||||
|
||||
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
|
||||
action->setChecked(getApp()->notifications->isChannelNotified(
|
||||
this->split_->getChannel()->getName(), Platform::Twitch));
|
||||
});
|
||||
action->connect(action, &QAction::triggered, this, [this]() {
|
||||
getApp()->notifications->updateChannelNotification(
|
||||
this->split_->getChannel()->getName(), Platform::Twitch);
|
||||
});
|
||||
QObject::connect(moreMenu, &QMenu::aboutToShow, this, [action, this]() {
|
||||
action->setChecked(getApp()->notifications->isChannelNotified(
|
||||
this->split_->getChannel()->getName(), Platform::Twitch));
|
||||
});
|
||||
action->connect(action, &QAction::triggered, this, [this]() {
|
||||
getApp()->notifications->updateChannelNotification(
|
||||
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);
|
||||
action->setText("Mute highlight sound");
|
||||
|
@ -332,11 +343,16 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
|||
}
|
||||
|
||||
moreMenu->addSeparator();
|
||||
moreMenu->addAction("Reconnect", this, SLOT(reconnect()));
|
||||
moreMenu->addAction("Reload channel emotes", this,
|
||||
SLOT(reloadChannelEmotes()));
|
||||
moreMenu->addAction("Reload subscriber emotes", this,
|
||||
SLOT(reloadSubscriberEmotes()));
|
||||
if (this->split_->getChannel()->canReconnect())
|
||||
moreMenu->addAction("Reconnect", this, SLOT(reconnect()));
|
||||
|
||||
if (dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||
{
|
||||
moreMenu->addAction("Reload channel emotes", this,
|
||||
SLOT(reloadChannelEmotes()));
|
||||
moreMenu->addAction("Reload subscriber emotes", this,
|
||||
SLOT(reloadSubscriberEmotes()));
|
||||
}
|
||||
moreMenu->addSeparator();
|
||||
moreMenu->addAction("Clear messages", this->split_, &Split::clear);
|
||||
// moreMenu->addSeparator();
|
||||
|
@ -725,7 +741,7 @@ void SplitHeader::reloadSubscriberEmotes()
|
|||
|
||||
void SplitHeader::reconnect()
|
||||
{
|
||||
getApp()->twitch.server->connect();
|
||||
this->split_->getChannel()->reconnect();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "messages/Link.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
|
Loading…
Reference in a new issue