Remove experimental IRC support (#5547)
This commit is contained in:
@@ -1,435 +0,0 @@
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// Ratelimits for joinBucket_
|
||||
const int JOIN_RATELIMIT_BUDGET = 18;
|
||||
const int JOIN_RATELIMIT_COOLDOWN = 12500;
|
||||
|
||||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
// XXX: don't create write connection if there is no separate write connection.
|
||||
this->writeConnection_.reset(new IrcConnection);
|
||||
this->writeConnection_->moveToThread(
|
||||
QCoreApplication::instance()->thread());
|
||||
|
||||
// Apply a leaky bucket rate limiting to JOIN messages
|
||||
auto actuallyJoin = [&](QString message) {
|
||||
if (!this->channels.contains(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->readConnection_->sendRaw("JOIN #" + message);
|
||||
};
|
||||
this->joinBucket_.reset(new RatelimitBucket(
|
||||
JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this));
|
||||
|
||||
QObject::connect(this->writeConnection_.get(),
|
||||
&Communi::IrcConnection::messageReceived, this,
|
||||
[this](auto msg) {
|
||||
this->writeConnectionMessageReceived(msg);
|
||||
});
|
||||
QObject::connect(this->writeConnection_.get(),
|
||||
&Communi::IrcConnection::connected, this, [this] {
|
||||
this->onWriteConnected(this->writeConnection_.get());
|
||||
});
|
||||
this->connections_.managedConnect(
|
||||
this->writeConnection_->connectionLost, [this](bool timeout) {
|
||||
qCDebug(chatterinoIrc)
|
||||
<< "Write connection reconnect requested. Timeout:" << timeout;
|
||||
this->writeConnection_->smartReconnect();
|
||||
});
|
||||
|
||||
// Listen to read connection message signals
|
||||
this->readConnection_.reset(new IrcConnection);
|
||||
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::messageReceived, this,
|
||||
[this](auto msg) {
|
||||
this->readConnectionMessageReceived(msg);
|
||||
});
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::privateMessageReceived, this,
|
||||
[this](auto msg) {
|
||||
this->privateMessageReceived(msg);
|
||||
});
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::connected, this, [this] {
|
||||
this->onReadConnected(this->readConnection_.get());
|
||||
});
|
||||
QObject::connect(this->readConnection_.get(),
|
||||
&Communi::IrcConnection::disconnected, this, [this] {
|
||||
this->onDisconnected();
|
||||
});
|
||||
this->connections_.managedConnect(
|
||||
this->readConnection_->connectionLost, [this](bool timeout) {
|
||||
qCDebug(chatterinoIrc)
|
||||
<< "Read connection reconnect requested. Timeout:" << timeout;
|
||||
if (timeout)
|
||||
{
|
||||
// Show additional message since this is going to interrupt a
|
||||
// connection that is still "connected"
|
||||
this->addGlobalSystemMessage(
|
||||
"Server connection timed out, reconnecting");
|
||||
}
|
||||
this->readConnection_->smartReconnect();
|
||||
});
|
||||
this->connections_.managedConnect(this->readConnection_->heartbeat, [this] {
|
||||
this->markChannelsConnected();
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractIrcServer::initializeIrc()
|
||||
{
|
||||
assert(!this->initialized_);
|
||||
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->initializeConnectionSignals(this->writeConnection_.get(),
|
||||
ConnectionType::Write);
|
||||
this->initializeConnectionSignals(this->readConnection_.get(),
|
||||
ConnectionType::Read);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->initializeConnectionSignals(this->readConnection_.get(),
|
||||
ConnectionType::Both);
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::connect()
|
||||
{
|
||||
assert(this->initialized_);
|
||||
|
||||
this->disconnect();
|
||||
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->initializeConnection(this->writeConnection_.get(), Write);
|
||||
this->initializeConnection(this->readConnection_.get(), Read);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->initializeConnection(this->readConnection_.get(), Both);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::open(ConnectionType type)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->connectionMutex_);
|
||||
|
||||
if (type == Write)
|
||||
{
|
||||
this->writeConnection_->open();
|
||||
}
|
||||
if (type & Read)
|
||||
{
|
||||
this->readConnection_->open();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::addGlobalSystemMessage(const QString &messageText)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
MessageBuilder b(systemMessage, messageText);
|
||||
auto message = b.release();
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(message, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::disconnect()
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
this->readConnection_->close();
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->close();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||
const QString &message)
|
||||
{
|
||||
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
||||
}
|
||||
|
||||
void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
||||
|
||||
if (this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnection_->sendRaw(rawMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
(void)message;
|
||||
}
|
||||
|
||||
ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
// try get channel
|
||||
ChannelPtr chan = this->getChannelOrEmpty(channelName);
|
||||
if (chan != Channel::getEmpty())
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// value doesn't exist
|
||||
chan = this->createChannel(channelName);
|
||||
if (!chan)
|
||||
{
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
this->channels.insert(channelName, chan);
|
||||
this->connections_.managedConnect(chan->destroyed, [this, channelName] {
|
||||
// fourtf: issues when the server itself is destroyed
|
||||
|
||||
qCDebug(chatterinoIrc) << "[AbstractIrcServer::addChannel]"
|
||||
<< channelName << "was destroyed";
|
||||
this->channels.remove(channelName);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
this->readConnection_->sendRaw("PART #" + channelName);
|
||||
}
|
||||
});
|
||||
|
||||
// join IRC channel
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
|
||||
|
||||
if (this->readConnection_)
|
||||
{
|
||||
if (this->readConnection_->isConnected())
|
||||
{
|
||||
this->joinBucket_->send(channelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
|
||||
{
|
||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
// try get special channel
|
||||
ChannelPtr chan = this->getCustomChannel(channelName);
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
|
||||
// value exists
|
||||
auto it = this->channels.find(channelName);
|
||||
if (it != this->channels.end())
|
||||
{
|
||||
chan = it.value().lock();
|
||||
|
||||
if (chan)
|
||||
{
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
|
||||
return Channel::getEmpty();
|
||||
}
|
||||
|
||||
std::vector<std::weak_ptr<Channel>> AbstractIrcServer::getChannels()
|
||||
{
|
||||
std::lock_guard lock(this->channelMutex);
|
||||
std::vector<std::weak_ptr<Channel>> channels;
|
||||
|
||||
for (auto &&weak : this->channels.values())
|
||||
{
|
||||
channels.push_back(weak);
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
(void)connection;
|
||||
|
||||
std::lock_guard lock(this->channelMutex);
|
||||
|
||||
// join channels
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto channel = weak.lock())
|
||||
{
|
||||
this->joinBucket_->send(channel->getName());
|
||||
}
|
||||
}
|
||||
|
||||
// connected/disconnected message
|
||||
auto connectedMsg = makeSystemMessage("connected");
|
||||
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||
auto reconnected = makeSystemMessage("reconnected");
|
||||
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
||||
|
||||
bool replaceMessage =
|
||||
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
|
||||
MessageFlag::DisconnectedMessage);
|
||||
|
||||
if (replaceMessage)
|
||||
{
|
||||
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
|
||||
}
|
||||
else
|
||||
{
|
||||
chan->addMessage(connectedMsg, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
|
||||
this->falloffCounter_ = 1;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
|
||||
{
|
||||
(void)connection;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::onDisconnected()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
MessageBuilder b(systemMessage, "disconnected");
|
||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||
auto disconnectedMsg = b.release();
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
std::shared_ptr<Channel> chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(disconnectedMsg, MessageContext::Original);
|
||||
|
||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||
{
|
||||
channel->markDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::markChannelsConnected()
|
||||
{
|
||||
this->forEachChannel([](const ChannelPtr &chan) {
|
||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||
{
|
||||
channel->markConnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||
const QString &channelName)
|
||||
{
|
||||
(void)channelName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||
{
|
||||
// This function is a Noop only for IRC, for Twitch it removes a leading '#' and lowercases the name
|
||||
return dirtyChannelName;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
{
|
||||
auto *fakeMessage = Communi::IrcMessage::fromData(
|
||||
data.toUtf8(), this->readConnection_.get());
|
||||
|
||||
if (fakeMessage->command() == "PRIVMSG")
|
||||
{
|
||||
this->privateMessageReceived(
|
||||
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->readConnectionMessageReceived(fakeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractIrcServer::privateMessageReceived(
|
||||
Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
(void)message;
|
||||
}
|
||||
|
||||
void AbstractIrcServer::readConnectionMessageReceived(
|
||||
Communi::IrcMessage *message)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||
|
||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||
{
|
||||
ChannelPtr chan = weak.lock();
|
||||
if (!chan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
func(chan);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,135 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
#include "util/RatelimitBucket.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
class RatelimitBucket;
|
||||
|
||||
class IAbstractIrcServer
|
||||
{
|
||||
public:
|
||||
virtual void connect() = 0;
|
||||
|
||||
virtual void sendRawMessage(const QString &rawMessage) = 0;
|
||||
|
||||
virtual ChannelPtr getOrAddChannel(const QString &dirtyChannelName) = 0;
|
||||
virtual ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) = 0;
|
||||
|
||||
virtual void addFakeMessage(const QString &data) = 0;
|
||||
|
||||
virtual void addGlobalSystemMessage(const QString &messageText) = 0;
|
||||
|
||||
virtual void forEachChannel(std::function<void(ChannelPtr)> func) = 0;
|
||||
};
|
||||
|
||||
class AbstractIrcServer : public IAbstractIrcServer, public QObject
|
||||
{
|
||||
public:
|
||||
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
|
||||
|
||||
~AbstractIrcServer() override = default;
|
||||
AbstractIrcServer(const AbstractIrcServer &) = delete;
|
||||
AbstractIrcServer(AbstractIrcServer &&) = delete;
|
||||
AbstractIrcServer &operator=(const AbstractIrcServer &) = delete;
|
||||
AbstractIrcServer &operator=(AbstractIrcServer &&) = delete;
|
||||
|
||||
// initializeIrc must be called from the derived class
|
||||
// this allows us to initialize the abstract IRC server based on the derived class's parameters
|
||||
void initializeIrc();
|
||||
|
||||
// connection
|
||||
void connect() final;
|
||||
void disconnect();
|
||||
|
||||
void sendMessage(const QString &channelName, const QString &message);
|
||||
void sendRawMessage(const QString &rawMessage) override;
|
||||
|
||||
// channels
|
||||
ChannelPtr getOrAddChannel(const QString &dirtyChannelName) final;
|
||||
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) final;
|
||||
std::vector<std::weak_ptr<Channel>> getChannels();
|
||||
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal connected;
|
||||
pajlada::Signals::NoArgSignal disconnected;
|
||||
|
||||
void addFakeMessage(const QString &data) final;
|
||||
|
||||
void addGlobalSystemMessage(const QString &messageText) final;
|
||||
|
||||
// iteration
|
||||
void forEachChannel(std::function<void(ChannelPtr)> func) final;
|
||||
|
||||
protected:
|
||||
AbstractIrcServer();
|
||||
|
||||
// initializeConnectionSignals is called on a connection once in its lifetime.
|
||||
// it can be used to connect signals to your class
|
||||
virtual void initializeConnectionSignals(IrcConnection *connection,
|
||||
ConnectionType type)
|
||||
{
|
||||
(void)connection;
|
||||
(void)type;
|
||||
}
|
||||
|
||||
// initializeConnection is called every time before we try to connect to the IRC server
|
||||
virtual void initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type) = 0;
|
||||
|
||||
virtual std::shared_ptr<Channel> createChannel(
|
||||
const QString &channelName) = 0;
|
||||
|
||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
|
||||
|
||||
virtual void onReadConnected(IrcConnection *connection);
|
||||
virtual void onWriteConnected(IrcConnection *connection);
|
||||
virtual void onDisconnected();
|
||||
void markChannelsConnected();
|
||||
|
||||
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||
const QString &channelName);
|
||||
|
||||
virtual bool hasSeparateWriteConnection() const = 0;
|
||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||
|
||||
void open(ConnectionType type);
|
||||
|
||||
QMap<QString, std::weak_ptr<Channel>> channels;
|
||||
std::mutex channelMutex;
|
||||
|
||||
private:
|
||||
void initConnection();
|
||||
|
||||
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
||||
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
// Our rate limiting bucket for the Twitch join rate limits
|
||||
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
||||
QObjectPtr<RatelimitBucket> joinBucket_;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
||||
std::mutex connectionMutex_;
|
||||
|
||||
// bool autoReconnect_ = false;
|
||||
pajlada::Signals::SignalHolder connections_;
|
||||
|
||||
bool initialized_{false};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,289 +0,0 @@
|
||||
#include "providers/irc/Irc2.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Credentials.hpp"
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
#include <pajlada/serialize.hpp>
|
||||
#include <QSaveFile>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
QString configPath()
|
||||
{
|
||||
return combinePath(getApp()->getPaths().settingsDirectory, "irc.json");
|
||||
}
|
||||
|
||||
class Model : public SignalVectorModel<IrcServerData>
|
||||
{
|
||||
public:
|
||||
Model(QObject *parent)
|
||||
: SignalVectorModel<IrcServerData>(6, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
IrcServerData getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const IrcServerData &original) override
|
||||
{
|
||||
return IrcServerData{
|
||||
row[0]->data(Qt::EditRole).toString(), // host
|
||||
row[1]->data(Qt::EditRole).toInt(), // port
|
||||
row[2]->data(Qt::CheckStateRole).toBool(), // ssl
|
||||
row[3]->data(Qt::EditRole).toString(), // user
|
||||
row[4]->data(Qt::EditRole).toString(), // nick
|
||||
row[5]->data(Qt::EditRole).toString(), // real
|
||||
original.authType, // authType
|
||||
original.connectCommands, // connectCommands
|
||||
original.id, // id
|
||||
};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void getRowFromItem(const IrcServerData &item,
|
||||
std::vector<QStandardItem *> &row) override
|
||||
{
|
||||
setStringItem(row[0], item.host, false);
|
||||
setStringItem(row[1], QString::number(item.port));
|
||||
setBoolItem(row[2], item.ssl);
|
||||
setStringItem(row[3], item.user);
|
||||
setStringItem(row[4], item.nick);
|
||||
setStringItem(row[5], item.real);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
inline QString escape(QString str)
|
||||
{
|
||||
return str.replace(":", "::");
|
||||
}
|
||||
|
||||
// This returns a unique id for every server which is understandeable in the systems credential manager.
|
||||
inline QString getCredentialName(const IrcServerData &data)
|
||||
{
|
||||
return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" +
|
||||
escape(data.host);
|
||||
}
|
||||
|
||||
void IrcServerData::getPassword(
|
||||
QObject *receiver, std::function<void(const QString &)> &&onLoaded) const
|
||||
{
|
||||
Credentials::instance().get("irc", getCredentialName(*this), receiver,
|
||||
std::move(onLoaded));
|
||||
}
|
||||
|
||||
void IrcServerData::setPassword(const QString &password)
|
||||
{
|
||||
Credentials::instance().set("irc", getCredentialName(*this), password);
|
||||
}
|
||||
|
||||
Irc::Irc()
|
||||
{
|
||||
// We can safely ignore this signal connection since `connections` will always
|
||||
// be destroyed before the Irc object
|
||||
std::ignore = this->connections.itemInserted.connect([this](auto &&args) {
|
||||
// make sure only one id can only exist for one server
|
||||
assert(this->servers_.find(args.item.id) == this->servers_.end());
|
||||
|
||||
// add new server
|
||||
if (auto ab = this->abandonedChannels_.find(args.item.id);
|
||||
ab != this->abandonedChannels_.end())
|
||||
{
|
||||
auto server = std::make_unique<IrcServer>(args.item, ab->second);
|
||||
|
||||
// set server of abandoned channels
|
||||
for (auto weak : ab->second)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
if (auto *ircChannel =
|
||||
dynamic_cast<IrcChannel *>(shared.get()))
|
||||
{
|
||||
ircChannel->setServer(server.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add new server with abandoned channels
|
||||
this->servers_.emplace(args.item.id, std::move(server));
|
||||
this->abandonedChannels_.erase(ab);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add new server
|
||||
this->servers_.emplace(args.item.id,
|
||||
std::make_unique<IrcServer>(args.item));
|
||||
}
|
||||
});
|
||||
|
||||
// We can safely ignore this signal connection since `connections` will always
|
||||
// be destroyed before the Irc object
|
||||
std::ignore = this->connections.itemRemoved.connect([this](auto &&args) {
|
||||
// restore
|
||||
if (auto server = this->servers_.find(args.item.id);
|
||||
server != this->servers_.end())
|
||||
{
|
||||
auto abandoned = server->second->getChannels();
|
||||
|
||||
// set server of abandoned servers to nullptr
|
||||
for (auto weak : abandoned)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
if (auto *ircChannel =
|
||||
dynamic_cast<IrcChannel *>(shared.get()))
|
||||
{
|
||||
ircChannel->setServer(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->abandonedChannels_[args.item.id] = abandoned;
|
||||
this->servers_.erase(server);
|
||||
}
|
||||
|
||||
if (args.caller != Irc::noEraseCredentialCaller)
|
||||
{
|
||||
Credentials::instance().erase("irc", getCredentialName(args.item));
|
||||
}
|
||||
});
|
||||
|
||||
// We can safely ignore this signal connection since `connections` will always
|
||||
// be destroyed before the Irc object
|
||||
std::ignore = this->connections.delayedItemsChanged.connect([this] {
|
||||
this->save();
|
||||
});
|
||||
}
|
||||
|
||||
QAbstractTableModel *Irc::newConnectionModel(QObject *parent)
|
||||
{
|
||||
auto *model = new Model(parent);
|
||||
model->initialize(&this->connections);
|
||||
return model;
|
||||
}
|
||||
|
||||
ChannelPtr Irc::getOrAddChannel(int id, QString name)
|
||||
{
|
||||
if (auto server = this->servers_.find(id); server != this->servers_.end())
|
||||
{
|
||||
return server->second->getOrAddChannel(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto channel = std::make_shared<IrcChannel>(name, nullptr);
|
||||
|
||||
this->abandonedChannels_[id].push_back(channel);
|
||||
|
||||
return std::move(channel);
|
||||
}
|
||||
}
|
||||
|
||||
Irc &Irc::instance()
|
||||
{
|
||||
static Irc irc;
|
||||
return irc;
|
||||
}
|
||||
|
||||
int Irc::uniqueId()
|
||||
{
|
||||
int i = this->currentId_ + 1;
|
||||
auto it = this->servers_.find(i);
|
||||
auto it2 = this->abandonedChannels_.find(i);
|
||||
|
||||
while (it != this->servers_.end() || it2 != this->abandonedChannels_.end())
|
||||
{
|
||||
i++;
|
||||
it = this->servers_.find(i);
|
||||
it2 = this->abandonedChannels_.find(i);
|
||||
}
|
||||
|
||||
return (this->currentId_ = i);
|
||||
}
|
||||
|
||||
void Irc::save()
|
||||
{
|
||||
QJsonDocument doc;
|
||||
QJsonObject root;
|
||||
QJsonArray servers;
|
||||
|
||||
for (auto &&conn : this->connections)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert("host", conn.host);
|
||||
obj.insert("port", conn.port);
|
||||
obj.insert("ssl", conn.ssl);
|
||||
obj.insert("username", conn.user);
|
||||
obj.insert("nickname", conn.nick);
|
||||
obj.insert("realname", conn.real);
|
||||
obj.insert("connectCommands",
|
||||
QJsonArray::fromStringList(conn.connectCommands));
|
||||
obj.insert("id", conn.id);
|
||||
obj.insert("authType", int(conn.authType));
|
||||
|
||||
servers.append(obj);
|
||||
}
|
||||
|
||||
root.insert("servers", servers);
|
||||
doc.setObject(root);
|
||||
|
||||
QSaveFile file(configPath());
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.write(doc.toJson());
|
||||
file.commit();
|
||||
}
|
||||
|
||||
void Irc::load()
|
||||
{
|
||||
if (this->loaded_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->loaded_ = true;
|
||||
|
||||
QString config = configPath();
|
||||
QFile file(configPath());
|
||||
file.open(QIODevice::ReadOnly);
|
||||
auto object = QJsonDocument::fromJson(file.readAll()).object();
|
||||
|
||||
std::unordered_set<int> ids;
|
||||
|
||||
// load servers
|
||||
for (auto server : object.value("servers").toArray())
|
||||
{
|
||||
auto obj = server.toObject();
|
||||
IrcServerData data;
|
||||
data.host = obj.value("host").toString(data.host);
|
||||
data.port = obj.value("port").toInt(data.port);
|
||||
data.ssl = obj.value("ssl").toBool(data.ssl);
|
||||
data.user = obj.value("username").toString(data.user);
|
||||
data.nick = obj.value("nickname").toString(data.nick);
|
||||
data.real = obj.value("realname").toString(data.real);
|
||||
data.connectCommands =
|
||||
obj.value("connectCommands").toVariant().toStringList();
|
||||
data.id = obj.value("id").toInt(data.id);
|
||||
data.authType =
|
||||
IrcAuthType(obj.value("authType").toInt(int(data.authType)));
|
||||
|
||||
// duplicate id's are not allowed :(
|
||||
if (ids.find(data.id) == ids.end())
|
||||
{
|
||||
ids.insert(data.id);
|
||||
|
||||
this->connections.append(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/SignalVector.hpp"
|
||||
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
class QAbstractTableModel;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
using ChannelPtr = std::shared_ptr<Channel>;
|
||||
class IrcServer;
|
||||
|
||||
enum class IrcAuthType { Anonymous, Custom, Pass, Sasl };
|
||||
|
||||
struct IrcServerData {
|
||||
QString host;
|
||||
int port = 6697;
|
||||
bool ssl = true;
|
||||
|
||||
QString user;
|
||||
QString nick;
|
||||
QString real;
|
||||
|
||||
IrcAuthType authType = IrcAuthType::Anonymous;
|
||||
void getPassword(QObject *receiver,
|
||||
std::function<void(const QString &)> &&onLoaded) const;
|
||||
void setPassword(const QString &password);
|
||||
|
||||
QStringList connectCommands;
|
||||
|
||||
int id;
|
||||
};
|
||||
|
||||
class Irc
|
||||
{
|
||||
public:
|
||||
Irc();
|
||||
|
||||
static Irc &instance();
|
||||
|
||||
static inline void *const noEraseCredentialCaller =
|
||||
reinterpret_cast<void *>(1);
|
||||
|
||||
SignalVector<IrcServerData> connections;
|
||||
QAbstractTableModel *newConnectionModel(QObject *parent);
|
||||
|
||||
ChannelPtr getOrAddChannel(int serverId, QString name);
|
||||
|
||||
void save();
|
||||
void load();
|
||||
|
||||
int uniqueId();
|
||||
|
||||
private:
|
||||
int currentId_{};
|
||||
bool loaded_{};
|
||||
|
||||
// Servers have a unique id.
|
||||
// When a server gets changed it gets removed and then added again.
|
||||
// So we store the channels of that server in abandonedChannels_ temporarily.
|
||||
// Or if the server got removed permanently then it's still stored there.
|
||||
std::unordered_map<int, std::unique_ptr<IrcServer>> servers_;
|
||||
std::unordered_map<int, std::vector<std::weak_ptr<Channel>>>
|
||||
abandonedChannels_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,36 +0,0 @@
|
||||
#include "providers/irc/IrcAccount.hpp"
|
||||
|
||||
// namespace chatterino {
|
||||
//
|
||||
// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName,
|
||||
// const QString
|
||||
// &_realName,
|
||||
// const QString &_password)
|
||||
// : userName(_userName)
|
||||
// , nickName(_nickName)
|
||||
// , realName(_realName)
|
||||
// , password(_password)
|
||||
//{
|
||||
//}
|
||||
|
||||
// const QString &IrcAccount::getUserName() const
|
||||
//{
|
||||
// return this->userName;
|
||||
//}
|
||||
|
||||
// const QString &IrcAccount::getNickName() const
|
||||
//{
|
||||
// return this->nickName;
|
||||
//}
|
||||
|
||||
// const QString &IrcAccount::getRealName() const
|
||||
//{
|
||||
// return this->realName;
|
||||
//}
|
||||
|
||||
// const QString &IrcAccount::getPassword() const
|
||||
//{
|
||||
// return this->password;
|
||||
//}
|
||||
//
|
||||
//} // namespace chatterino
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
// namespace chatterino {
|
||||
//
|
||||
// class IrcAccount
|
||||
//{
|
||||
// public:
|
||||
// IrcAccount(const QString &userName, const QString &nickName, const QString
|
||||
// &realName,
|
||||
// const QString &password);
|
||||
|
||||
// const QString &getUserName() const;
|
||||
// const QString &getNickName() const;
|
||||
// const QString &getRealName() const;
|
||||
// const QString &getPassword() const;
|
||||
|
||||
// private:
|
||||
// QString userName;
|
||||
// QString nickName;
|
||||
// QString realName;
|
||||
// QString password;
|
||||
//};
|
||||
//
|
||||
//} // namespace chatterino
|
||||
@@ -1,119 +0,0 @@
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/irc/IrcCommands.hpp"
|
||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
IrcChannel::IrcChannel(const QString &name, IrcServer *server)
|
||||
: Channel(name, Channel::Type::Irc)
|
||||
, ChannelChatters(*static_cast<Channel *>(this))
|
||||
, server_(server)
|
||||
{
|
||||
auto *ircServer = this->server();
|
||||
if (ircServer != nullptr)
|
||||
{
|
||||
this->platform_ =
|
||||
QString("irc-%1").arg(ircServer->userFriendlyIdentifier());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->platform_ = "irc-unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void IrcChannel::sendMessage(const QString &message)
|
||||
{
|
||||
assertInGuiThread();
|
||||
if (message.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.startsWith("/"))
|
||||
{
|
||||
auto index = message.indexOf(' ', 1);
|
||||
QString command = message.mid(1, index - 1);
|
||||
QString params = index == -1 ? "" : message.mid(index + 1);
|
||||
|
||||
invokeIrcCommand(command, params, *this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->server() != nullptr)
|
||||
{
|
||||
this->server()->sendMessage(this->getName(), message);
|
||||
if (this->server()->hasEcho())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBuilder builder;
|
||||
|
||||
builder
|
||||
.emplace<TextElement>("#" + this->getName(),
|
||||
MessageElementFlag::ChannelName,
|
||||
MessageColor::System)
|
||||
->setLink({Link::JumpToChannel, this->getName()});
|
||||
|
||||
auto now = QDateTime::currentDateTime();
|
||||
builder.emplace<TimestampElement>(now.time());
|
||||
builder.message().serverReceivedTime = now;
|
||||
|
||||
auto username = this->server()->nick();
|
||||
builder
|
||||
.emplace<TextElement>(
|
||||
username + ":", MessageElementFlag::Username,
|
||||
getRandomColor(username), FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, username});
|
||||
builder.message().loginName = username;
|
||||
builder.message().displayName = username;
|
||||
|
||||
// message
|
||||
builder.addIrcMessageText(message);
|
||||
builder.message().messageText = message;
|
||||
builder.message().searchText = username + ": " + message;
|
||||
|
||||
this->addMessage(builder.release(), MessageContext::Original);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->addSystemMessage("You are not connected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IrcServer *IrcChannel::server() const
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
return this->server_;
|
||||
}
|
||||
|
||||
void IrcChannel::setServer(IrcServer *server)
|
||||
{
|
||||
assertInGuiThread();
|
||||
|
||||
this->server_ = server;
|
||||
}
|
||||
|
||||
bool IrcChannel::canReconnect() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void IrcChannel::reconnect()
|
||||
{
|
||||
if (this->server())
|
||||
{
|
||||
this->server()->connect();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/ChannelChatters.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Irc;
|
||||
class IrcServer;
|
||||
|
||||
class IrcChannel final : public Channel, public ChannelChatters
|
||||
{
|
||||
public:
|
||||
explicit IrcChannel(const QString &name, IrcServer *server);
|
||||
|
||||
void sendMessage(const QString &message) override;
|
||||
|
||||
// server may be nullptr
|
||||
IrcServer *server() const;
|
||||
|
||||
// Channel methods
|
||||
bool canReconnect() const override;
|
||||
void reconnect() override;
|
||||
|
||||
private:
|
||||
void setServer(IrcServer *server);
|
||||
|
||||
IrcServer *server_;
|
||||
|
||||
friend class Irc;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,92 +0,0 @@
|
||||
#include "providers/irc/IrcCommands.hpp"
|
||||
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Outcome invokeIrcCommand(const QString &commandName, const QString &allParams,
|
||||
IrcChannel &channel)
|
||||
{
|
||||
if (!channel.server())
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
// STATIC MESSAGES
|
||||
static auto staticMessages = std::unordered_map<QString, QString>{
|
||||
{"join", "/join is not supported. Press ctrl+r to change the "
|
||||
"channel. If required use /raw JOIN #channel."},
|
||||
{"part", "/part is not supported. Press ctrl+r to change the "
|
||||
"channel. If required use /raw PART #channel."},
|
||||
};
|
||||
auto cmd = commandName.toLower();
|
||||
|
||||
if (auto it = staticMessages.find(cmd); it != staticMessages.end())
|
||||
{
|
||||
channel.addSystemMessage(it->second);
|
||||
return Success;
|
||||
}
|
||||
|
||||
// CUSTOM COMMANDS
|
||||
auto params = allParams.split(' ');
|
||||
auto paramsAfter = [&](int i) {
|
||||
return params.mid(i + 1).join(' ');
|
||||
};
|
||||
|
||||
auto sendRaw = [&](QString str) {
|
||||
channel.server()->sendRawMessage(str);
|
||||
};
|
||||
|
||||
if (cmd == "msg")
|
||||
{
|
||||
channel.server()->sendWhisper(params[0], paramsAfter(0));
|
||||
}
|
||||
else if (cmd == "away")
|
||||
{
|
||||
sendRaw("AWAY " + params[0] + " :" + paramsAfter(0));
|
||||
}
|
||||
else if (cmd == "knock")
|
||||
{
|
||||
sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0));
|
||||
}
|
||||
else if (cmd == "kick")
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
channel.addSystemMessage(
|
||||
"Usage: /kick <channel> <client> [message]");
|
||||
return Failure;
|
||||
}
|
||||
const auto &channelParam = params[0];
|
||||
const auto &clientParam = params[1];
|
||||
const auto &messageParam = paramsAfter(1);
|
||||
if (messageParam.isEmpty())
|
||||
{
|
||||
sendRaw("KICK " + channelParam + " " + clientParam);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendRaw("KICK " + channelParam + " " + clientParam + " :" +
|
||||
messageParam);
|
||||
}
|
||||
}
|
||||
else if (cmd == "wallops")
|
||||
{
|
||||
sendRaw("WALLOPS :" + allParams);
|
||||
}
|
||||
else if (cmd == "raw")
|
||||
{
|
||||
sendRaw(allParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendRaw(cmd.toUpper() + " " + allParams);
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Outcome.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class IrcChannel;
|
||||
|
||||
Outcome invokeIrcCommand(const QString &command, const QString ¶ms,
|
||||
IrcChannel &channel);
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
||||
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageColor.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/IrcHelpers.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
IrcMessageBuilder::IrcMessageBuilder(
|
||||
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args)
|
||||
: SharedMessageBuilder(_channel, _ircMessage, _args)
|
||||
{
|
||||
}
|
||||
|
||||
IrcMessageBuilder::IrcMessageBuilder(Channel *_channel,
|
||||
const Communi::IrcMessage *_ircMessage,
|
||||
const MessageParseArgs &_args,
|
||||
QString content, bool isAction)
|
||||
: SharedMessageBuilder(_channel, _ircMessage, _args, content, isAction)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
IrcMessageBuilder::IrcMessageBuilder(
|
||||
const Communi::IrcNoticeMessage *_ircMessage, const MessageParseArgs &_args)
|
||||
: SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args,
|
||||
_ircMessage->content(), false)
|
||||
{
|
||||
}
|
||||
|
||||
IrcMessageBuilder::IrcMessageBuilder(
|
||||
const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args)
|
||||
: SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args,
|
||||
_ircMessage->content(), false)
|
||||
, whisperTarget_(_ircMessage->target())
|
||||
{
|
||||
}
|
||||
|
||||
MessagePtr IrcMessageBuilder::build()
|
||||
{
|
||||
// PARSE
|
||||
this->parse();
|
||||
this->usernameColor_ = getRandomColor(this->ircMessage->nick());
|
||||
|
||||
// PUSH ELEMENTS
|
||||
this->appendChannelName();
|
||||
|
||||
this->message().serverReceivedTime = calculateMessageTime(this->ircMessage);
|
||||
this->emplace<TimestampElement>(this->message().serverReceivedTime.time());
|
||||
|
||||
this->appendUsername();
|
||||
|
||||
// message
|
||||
this->addIrcMessageText(this->originalMessage_);
|
||||
|
||||
QString stylizedUsername =
|
||||
this->stylizeUsername(this->userName, this->message());
|
||||
|
||||
this->message().searchText = stylizedUsername + " " +
|
||||
this->message().localizedName + " " +
|
||||
this->userName + ": " + this->originalMessage_;
|
||||
|
||||
// highlights
|
||||
this->parseHighlights();
|
||||
|
||||
// highlighting incoming whispers if requested per setting
|
||||
if (this->args.isReceivedWhisper && getSettings()->highlightInlineWhispers)
|
||||
{
|
||||
this->message().flags.set(MessageFlag::HighlightedWhisper, true);
|
||||
}
|
||||
|
||||
return this->release();
|
||||
}
|
||||
|
||||
void IrcMessageBuilder::appendUsername()
|
||||
{
|
||||
QString username = this->userName;
|
||||
this->message().loginName = username;
|
||||
this->message().displayName = username;
|
||||
|
||||
// The full string that will be rendered in the chat widget
|
||||
QString usernameText =
|
||||
SharedMessageBuilder::stylizeUsername(username, this->message());
|
||||
|
||||
if (this->args.isReceivedWhisper)
|
||||
{
|
||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
||||
this->usernameColor_,
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserWhisper, this->message().displayName});
|
||||
|
||||
// Separator
|
||||
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
||||
MessageColor::System, FontStyle::ChatMedium);
|
||||
|
||||
if (this->whisperTarget_.isEmpty())
|
||||
{
|
||||
this->emplace<TextElement>("you:", MessageElementFlag::Username);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplace<TextElement>(this->whisperTarget_ + ":",
|
||||
MessageElementFlag::Username,
|
||||
getRandomColor(this->whisperTarget_),
|
||||
FontStyle::ChatMediumBold);
|
||||
}
|
||||
}
|
||||
else if (this->args.isSentWhisper)
|
||||
{
|
||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
||||
this->usernameColor_,
|
||||
FontStyle::ChatMediumBold);
|
||||
|
||||
// Separator
|
||||
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
||||
MessageColor::System, FontStyle::ChatMedium);
|
||||
|
||||
this->emplace<TextElement>(
|
||||
this->whisperTarget_ + ":", MessageElementFlag::Username,
|
||||
getRandomColor(this->whisperTarget_), FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserWhisper, this->whisperTarget_});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->action_)
|
||||
{
|
||||
usernameText += ":";
|
||||
}
|
||||
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
||||
this->usernameColor_,
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, this->message().loginName});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "messages/SharedMessageBuilder.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Emote;
|
||||
using EmotePtr = std::shared_ptr<const Emote>;
|
||||
|
||||
class Channel;
|
||||
|
||||
class IrcMessageBuilder : public SharedMessageBuilder
|
||||
{
|
||||
public:
|
||||
IrcMessageBuilder() = delete;
|
||||
|
||||
explicit IrcMessageBuilder(Channel *_channel,
|
||||
const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args);
|
||||
explicit IrcMessageBuilder(Channel *_channel,
|
||||
const Communi::IrcMessage *_ircMessage,
|
||||
const MessageParseArgs &_args, QString content,
|
||||
bool isAction);
|
||||
|
||||
/**
|
||||
* @brief used for global notice messages (i.e. notice messages without a channel as its target)
|
||||
**/
|
||||
explicit IrcMessageBuilder(const Communi::IrcNoticeMessage *_ircMessage,
|
||||
const MessageParseArgs &_args);
|
||||
|
||||
/**
|
||||
* @brief used for whisper messages (i.e. PRIVMSG messages with our nick as the target)
|
||||
**/
|
||||
explicit IrcMessageBuilder(const Communi::IrcPrivateMessage *_ircMessage,
|
||||
const MessageParseArgs &_args);
|
||||
|
||||
MessagePtr build() override;
|
||||
|
||||
private:
|
||||
void appendUsername();
|
||||
|
||||
/**
|
||||
* @brief holds the name of the target for the private/direct IRC message
|
||||
*
|
||||
* This might not be our nick
|
||||
*/
|
||||
QString whisperTarget_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,386 +0,0 @@
|
||||
#include "providers/irc/IrcServer.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageColor.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/irc/Irc2.hpp"
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp" // NOTE: Included to access the mentions channel
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/IrcHelpers.hpp"
|
||||
|
||||
#include <QMetaEnum>
|
||||
#include <QPointer>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
IrcServer::IrcServer(const IrcServerData &data)
|
||||
: data_(new IrcServerData(data))
|
||||
{
|
||||
this->initializeIrc();
|
||||
|
||||
this->connect();
|
||||
}
|
||||
|
||||
IrcServer::IrcServer(const IrcServerData &data,
|
||||
const std::vector<std::weak_ptr<Channel>> &restoreChannels)
|
||||
: IrcServer(data)
|
||||
{
|
||||
for (auto &&weak : restoreChannels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
this->channels[shared->getName()] = weak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IrcServer::~IrcServer()
|
||||
{
|
||||
delete this->data_;
|
||||
}
|
||||
|
||||
int IrcServer::id()
|
||||
{
|
||||
return this->data_->id;
|
||||
}
|
||||
|
||||
const QString &IrcServer::user()
|
||||
{
|
||||
return this->data_->user;
|
||||
}
|
||||
|
||||
const QString &IrcServer::nick()
|
||||
{
|
||||
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
|
||||
}
|
||||
|
||||
const QString &IrcServer::userFriendlyIdentifier()
|
||||
{
|
||||
return this->data_->host;
|
||||
}
|
||||
|
||||
void IrcServer::initializeConnectionSignals(IrcConnection *connection,
|
||||
ConnectionType type)
|
||||
{
|
||||
assert(type == Both);
|
||||
|
||||
QObject::connect(
|
||||
connection, &Communi::IrcConnection::socketError, this,
|
||||
[this](QAbstractSocket::SocketError error) {
|
||||
static int index =
|
||||
QAbstractSocket::staticMetaObject.indexOfEnumerator(
|
||||
"SocketError");
|
||||
|
||||
std::lock_guard lock(this->channelMutex);
|
||||
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
shared->addSystemMessage(
|
||||
QStringLiteral("Socket error: ") +
|
||||
QAbstractSocket::staticMetaObject.enumerator(index)
|
||||
.valueToKey(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
|
||||
this, [](const QString &reserved, QString *result) {
|
||||
*result = QString("%1%2").arg(
|
||||
reserved, QString::number(std::rand() % 100));
|
||||
});
|
||||
|
||||
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
|
||||
this, [this](Communi::IrcNoticeMessage *message) {
|
||||
MessageParseArgs args;
|
||||
args.isReceivedWhisper = true;
|
||||
|
||||
IrcMessageBuilder builder(message, args);
|
||||
|
||||
auto msg = builder.build();
|
||||
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
shared->addMessage(msg,
|
||||
MessageContext::Original);
|
||||
}
|
||||
}
|
||||
});
|
||||
QObject::connect(connection,
|
||||
&Communi::IrcConnection::capabilityMessageReceived, this,
|
||||
[this](Communi::IrcCapabilityMessage *message) {
|
||||
const QStringList caps = message->capabilities();
|
||||
if (caps.contains("echo-message"))
|
||||
{
|
||||
this->hasEcho_ = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IrcServer::initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type)
|
||||
{
|
||||
assert(type == Both);
|
||||
|
||||
connection->setSecure(this->data_->ssl);
|
||||
connection->setHost(this->data_->host);
|
||||
connection->setPort(this->data_->port);
|
||||
|
||||
connection->setUserName(this->data_->user);
|
||||
connection->setNickName(this->data_->nick.isEmpty() ? this->data_->user
|
||||
: this->data_->nick);
|
||||
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
|
||||
: this->data_->nick);
|
||||
connection->network()->setRequestedCapabilities({"echo-message"});
|
||||
|
||||
if (getSettings()->enableExperimentalIrc)
|
||||
{
|
||||
switch (this->data_->authType)
|
||||
{
|
||||
case IrcAuthType::Sasl:
|
||||
connection->setSaslMechanism("PLAIN");
|
||||
[[fallthrough]];
|
||||
case IrcAuthType::Pass:
|
||||
this->data_->getPassword(
|
||||
this, [conn = new QPointer(connection) /* can't copy */,
|
||||
this](const QString &password) mutable {
|
||||
if (*conn)
|
||||
{
|
||||
(*conn)->setPassword(password);
|
||||
this->open(Both);
|
||||
}
|
||||
|
||||
delete conn;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this->open(Both);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> IrcServer::createChannel(const QString &channelName)
|
||||
{
|
||||
return std::make_shared<IrcChannel>(channelName, this);
|
||||
}
|
||||
|
||||
bool IrcServer::hasSeparateWriteConnection() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void IrcServer::onReadConnected(IrcConnection *connection)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(this->channelMutex);
|
||||
|
||||
for (auto &&command : this->data_->connectCommands)
|
||||
{
|
||||
connection->sendRaw(command + "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
AbstractIrcServer::onReadConnected(connection);
|
||||
}
|
||||
|
||||
void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
// Note: This doesn't use isPrivate() because it only applies to messages targeting our user,
|
||||
// Servers or bouncers may send messages which have our user as the source
|
||||
// (like with echo-message CAP), we need to take care of this.
|
||||
if (!message->target().startsWith("#"))
|
||||
{
|
||||
MessageParseArgs args;
|
||||
if (message->isOwn())
|
||||
{
|
||||
// The server sent us a whisper which has our user as the source
|
||||
args.isSentWhisper = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.isReceivedWhisper = true;
|
||||
}
|
||||
|
||||
IrcMessageBuilder builder(message, args);
|
||||
|
||||
auto msg = builder.build();
|
||||
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
shared->addMessage(msg, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto target = message->target();
|
||||
target = target.startsWith('#') ? target.mid(1) : target;
|
||||
|
||||
if (auto channel = this->getChannelOrEmpty(target); !channel->isEmpty())
|
||||
{
|
||||
MessageParseArgs args;
|
||||
IrcMessageBuilder builder(channel.get(), message, args);
|
||||
|
||||
if (!builder.isIgnored())
|
||||
{
|
||||
auto msg = builder.build();
|
||||
|
||||
channel->addMessage(msg, MessageContext::Original);
|
||||
builder.triggerHighlights();
|
||||
const auto highlighted = msg->flags.has(MessageFlag::Highlighted);
|
||||
const auto showInMentions =
|
||||
msg->flags.has(MessageFlag::ShowInMentions);
|
||||
|
||||
if (highlighted && showInMentions)
|
||||
{
|
||||
getApp()->getTwitch()->getMentionsChannel()->addMessage(
|
||||
msg, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoIrc) << "message ignored :rage:";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
AbstractIrcServer::readConnectionMessageReceived(message);
|
||||
|
||||
switch (message->type())
|
||||
{
|
||||
case Communi::IrcMessage::Join: {
|
||||
auto *x = static_cast<Communi::IrcJoinMessage *>(message);
|
||||
|
||||
if (auto it = this->channels.find(x->channel());
|
||||
it != this->channels.end())
|
||||
{
|
||||
if (auto shared = it->lock())
|
||||
{
|
||||
if (message->nick() == this->data_->nick)
|
||||
{
|
||||
shared->addSystemMessage("joined");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto *c =
|
||||
dynamic_cast<ChannelChatters *>(shared.get()))
|
||||
{
|
||||
c->addJoinedUser(x->nick());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Communi::IrcMessage::Part: {
|
||||
auto *x = static_cast<Communi::IrcPartMessage *>(message);
|
||||
|
||||
if (auto it = this->channels.find(x->channel());
|
||||
it != this->channels.end())
|
||||
{
|
||||
if (auto shared = it->lock())
|
||||
{
|
||||
if (message->nick() == this->data_->nick)
|
||||
{
|
||||
shared->addSystemMessage("parted");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto *c =
|
||||
dynamic_cast<ChannelChatters *>(shared.get()))
|
||||
{
|
||||
c->addPartedUser(x->nick());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case Communi::IrcMessage::Pong:
|
||||
case Communi::IrcMessage::Notice:
|
||||
case Communi::IrcMessage::Private:
|
||||
return;
|
||||
|
||||
default:
|
||||
if (getSettings()->showUnhandledIrcMessages)
|
||||
{
|
||||
MessageBuilder builder;
|
||||
|
||||
builder.emplace<TimestampElement>(
|
||||
calculateMessageTime(message).time());
|
||||
builder.emplace<TextElement>(message->toData(),
|
||||
MessageElementFlag::Text);
|
||||
builder->flags.set(MessageFlag::Debug);
|
||||
|
||||
auto msg = builder.release();
|
||||
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
shared->addMessage(msg, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void IrcServer::sendWhisper(const QString &target, const QString &message)
|
||||
{
|
||||
this->sendRawMessage(QString("PRIVMSG %1 :%2").arg(target, message));
|
||||
if (this->hasEcho())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessageParseArgs args;
|
||||
args.isSentWhisper = true;
|
||||
|
||||
MessageBuilder b;
|
||||
|
||||
b.emplace<TimestampElement>();
|
||||
b.emplace<TextElement>(this->nick(), MessageElementFlag::Text,
|
||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||
b.emplace<TextElement>("->", MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
b.emplace<TextElement>(target + ":", MessageElementFlag::Text,
|
||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||
b.emplace<TextElement>(message, MessageElementFlag::Text);
|
||||
|
||||
auto msg = b.release();
|
||||
for (auto &&weak : this->channels)
|
||||
{
|
||||
if (auto shared = weak.lock())
|
||||
{
|
||||
shared->addMessage(msg, MessageContext::Original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IrcServer::sendRawMessage(const QString &rawMessage)
|
||||
{
|
||||
AbstractIrcServer::sendRawMessage(rawMessage.left(510));
|
||||
}
|
||||
|
||||
bool IrcServer::hasEcho() const
|
||||
{
|
||||
return this->hasEcho_;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/irc/AbstractIrcServer.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct IrcServerData;
|
||||
|
||||
class IrcServer : public AbstractIrcServer
|
||||
{
|
||||
public:
|
||||
explicit IrcServer(const IrcServerData &data);
|
||||
IrcServer(const IrcServerData &data,
|
||||
const std::vector<std::weak_ptr<Channel>> &restoreChannels);
|
||||
~IrcServer() override;
|
||||
|
||||
int id();
|
||||
const QString &user();
|
||||
const QString &nick();
|
||||
const QString &userFriendlyIdentifier();
|
||||
|
||||
bool hasEcho() const;
|
||||
/**
|
||||
* @brief sends a whisper to the target user (PRIVMSG where a user is the target)
|
||||
*/
|
||||
void sendWhisper(const QString &target, const QString &message);
|
||||
|
||||
void sendRawMessage(const QString &rawMessage) override;
|
||||
|
||||
// AbstractIrcServer interface
|
||||
protected:
|
||||
void initializeConnectionSignals(IrcConnection *connection,
|
||||
ConnectionType type) override;
|
||||
void initializeConnection(IrcConnection *connection,
|
||||
ConnectionType type) override;
|
||||
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
|
||||
bool hasSeparateWriteConnection() const override;
|
||||
|
||||
void onReadConnected(IrcConnection *connection) override;
|
||||
void privateMessageReceived(Communi::IrcPrivateMessage *message) override;
|
||||
void readConnectionMessageReceived(Communi::IrcMessage *message) override;
|
||||
|
||||
private:
|
||||
// pointer so we don't have to circle include Irc2.hpp
|
||||
IrcServerData *data_;
|
||||
|
||||
bool hasEcho_{false};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
Reference in New Issue
Block a user