Merge branch 'master' into irc-support

This commit is contained in:
fourtf
2019-09-18 13:03:16 +02:00
204 changed files with 17026 additions and 16467 deletions
+47 -9
View File
@@ -79,7 +79,38 @@ namespace {
return {Success, std::move(emotes)};
}
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
boost::optional<EmotePtr> parseModBadge(const QJsonObject &jsonRoot)
{
boost::optional<EmotePtr> modBadge;
auto room = jsonRoot.value("room").toObject();
auto modUrls = room.value("mod_urls").toObject();
if (!modUrls.isEmpty())
{
auto modBadge1x = getEmoteLink(modUrls, "1");
auto modBadge2x = getEmoteLink(modUrls, "2");
auto modBadge3x = getEmoteLink(modUrls, "4");
auto modBadgeImageSet = ImageSet{
Image::fromUrl(modBadge1x, 1),
modBadge2x.string.isEmpty() ? Image::getEmpty()
: Image::fromUrl(modBadge2x, 0.5),
modBadge3x.string.isEmpty() ? Image::getEmpty()
: Image::fromUrl(modBadge3x, 0.25),
};
modBadge = std::make_shared<Emote>(Emote{
{""},
modBadgeImageSet,
Tooltip{"Twitch Channel Moderator"},
modBadge1x,
});
}
return modBadge;
}
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
@@ -110,7 +141,7 @@ namespace {
}
}
return {Success, std::move(emotes)};
return emotes;
}
} // namespace
@@ -151,19 +182,26 @@ void FfzEmotes::loadEmotes()
.execute();
}
void FfzEmotes::loadChannel(const QString &channelId,
std::function<void(EmoteMap &&)> callback)
void FfzEmotes::loadChannel(
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
{
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
.timeout(20000)
.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson());
if (pair.first)
callback(std::move(pair.second));
return pair.first;
.onSuccess([emoteCallback = std::move(emoteCallback),
modBadgeCallback =
std::move(modBadgeCallback)](auto result) -> Outcome {
auto json = result.parseJson();
auto emoteMap = parseChannelEmotes(json);
auto modBadge = parseModBadge(json);
emoteCallback(std::move(emoteMap));
modBadgeCallback(std::move(modBadge));
return Success;
})
.onError([channelId](int result) {
if (result == 203)
+4 -2
View File
@@ -22,8 +22,10 @@ public:
std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelId,
std::function<void(EmoteMap &&)> callback);
static void loadChannel(
const QString &channelId,
std::function<void(EmoteMap &&)> emoteCallback,
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback);
private:
Atomic<std::shared_ptr<const EmoteMap>> global_;
-63
View File
@@ -1,63 +0,0 @@
#include "FfzModBadge.hpp"
#include <QBuffer>
#include <QImageReader>
#include <QJsonObject>
#include <QPainter>
#include <QString>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
FfzModBadge::FfzModBadge(const QString &channelName)
: channelName_(channelName)
{
}
void FfzModBadge::loadCustomModBadge()
{
static QString partialUrl("https://cdn.frankerfacez.com/room-badge/mod/");
QString url = partialUrl + channelName_ + "/1";
NetworkRequest(url)
.onSuccess([this, url](auto result) -> Outcome {
auto data = result.getData();
QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
if (reader.imageCount() == 0)
return Failure;
QPixmap badgeOverlay = QPixmap::fromImageReader(&reader);
QPixmap badgePixmap(18, 18);
// the default mod badge green color
badgePixmap.fill(QColor("#34AE0A"));
QPainter painter(&badgePixmap);
QRectF rect(0, 0, 18, 18);
painter.drawPixmap(rect, badgeOverlay, rect);
auto emote = Emote{{""},
ImageSet{Image::fromPixmap(badgePixmap)},
Tooltip{"Twitch Channel Moderator"},
Url{url}};
this->badge_ = std::make_shared<Emote>(emote);
// getBadge.execute();
return Success;
})
.execute();
}
EmotePtr FfzModBadge::badge() const
{
return this->badge_;
}
} // namespace chatterino
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <QString>
#include <boost/optional.hpp>
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class FfzModBadge
{
public:
FfzModBadge(const QString &channelName);
void loadCustomModBadge();
EmotePtr badge() const;
private:
const QString channelName_;
EmotePtr badge_;
};
} // namespace chatterino
+380 -380
View File
@@ -1,380 +1,380 @@
#include "AbstractIrcServer.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include <QCoreApplication>
namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
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, [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);
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(); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError, this,
[this] { this->onSocketError(); });
// listen to reconnect request
this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_);
this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_);
this->connect();
}
});
}
void AbstractIrcServer::connect()
{
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::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_.emplace_back(chan->destroyed.connect([this,
channelName] {
// fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
this->channels.remove(channelName);
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
// join irc channel
{
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_)
{
if (this->readConnection_->isConnected())
{
this->readConnection_->sendRaw("JOIN #" + channelName);
}
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
if (this->readConnection_->isConnected())
{
this->writeConnection_->sendRaw("JOIN #" + 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())
{
connection->sendRaw("JOIN #" + 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);
continue;
}
chan->addMessage(connectedMsg);
}
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);
}
}
void AbstractIrcServer::onSocketError()
{
this->reconnectTimer_.start();
}
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName)
{
(void)channelName;
return nullptr;
}
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1);
else
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
#include "AbstractIrcServer.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include <QCoreApplication>
namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
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, [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);
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(); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError, this,
[this] { this->onSocketError(); });
// listen to reconnect request
this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_);
this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_);
this->connect();
}
});
}
void AbstractIrcServer::connect()
{
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::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_.emplace_back(chan->destroyed.connect([this,
channelName] {
// fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
this->channels.remove(channelName);
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
// join irc channel
{
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_)
{
if (this->readConnection_->isConnected())
{
this->readConnection_->sendRaw("JOIN #" + channelName);
}
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
if (this->readConnection_->isConnected())
{
this->writeConnection_->sendRaw("JOIN #" + 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())
{
connection->sendRaw("JOIN #" + 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);
continue;
}
chan->addMessage(connectedMsg);
}
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);
}
}
void AbstractIrcServer::onSocketError()
{
this->reconnectTimer_.start();
}
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName)
{
(void)channelName;
return nullptr;
}
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1);
else
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
+95 -95
View File
@@ -1,95 +1,95 @@
#pragma once
#include "providers/irc/IrcConnection2.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 AbstractIrcServer : public QObject
{
public:
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
virtual ~AbstractIrcServer() = default;
// connection
void connect();
void disconnect();
void sendMessage(const QString &channelName, const QString &message);
void sendRawMessage(const QString &rawMessage);
// channels
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;
void addFakeMessage(const QString &data);
// iteration
void forEachChannel(std::function<void(ChannelPtr)> func);
protected:
AbstractIrcServer();
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();
virtual void onSocketError();
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();
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;
std::mutex connectionMutex_;
// bool autoReconnect_ = false;
pajlada::Signals::SignalHolder connections_;
};
} // namespace chatterino
#pragma once
#include "providers/irc/IrcConnection2.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 AbstractIrcServer : public QObject
{
public:
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
virtual ~AbstractIrcServer() = default;
// connection
void connect();
void disconnect();
void sendMessage(const QString &channelName, const QString &message);
void sendRawMessage(const QString &rawMessage);
// channels
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;
void addFakeMessage(const QString &data);
// iteration
void forEachChannel(std::function<void(ChannelPtr)> func);
protected:
AbstractIrcServer();
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();
virtual void onSocketError();
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();
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;
std::mutex connectionMutex_;
// bool autoReconnect_ = false;
pajlada::Signals::SignalHolder connections_;
};
} // namespace chatterino
+68 -68
View File
@@ -1,68 +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(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
#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(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
+33 -33
View File
@@ -1,33 +1,33 @@
#pragma once
#include "common/Channel.hpp"
#include "common/ChannelChatters.hpp"
namespace chatterino {
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
#pragma once
#include "common/Channel.hpp"
#include "common/ChannelChatters.hpp"
namespace chatterino {
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
+44 -44
View File
@@ -1,44 +1,44 @@
#include "IrcConnection2.hpp"
namespace chatterino {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
// send ping every x seconds
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING chatterino/ping");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
}
});
// reconnect after x seconds without receiving a message
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
reconnectRequested.invoke();
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive())
{
this->reconnectTimer_.stop();
}
});
}
} // namespace chatterino
#include "IrcConnection2.hpp"
namespace chatterino {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
// send ping every x seconds
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING chatterino/ping");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
}
});
// reconnect after x seconds without receiving a message
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
reconnectRequested.invoke();
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive())
{
this->reconnectTimer_.stop();
}
});
}
} // namespace chatterino
+23 -23
View File
@@ -1,23 +1,23 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace chatterino
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace chatterino
+259 -259
View File
@@ -1,259 +1,259 @@
#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 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:
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
#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 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:
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
+38 -38
View File
@@ -1,38 +1,38 @@
#pragma once
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/irc/IrcAccount.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();
// 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_;
};
} // namespace chatterino
#pragma once
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/irc/IrcAccount.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();
// 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_;
};
} // namespace chatterino
+9 -3
View File
@@ -48,9 +48,15 @@ void ChatroomChannel::refreshFFZChannelEmotes()
{
return;
}
FfzEmotes::loadChannel(this->chatroomOwnerId, [this](auto &&emoteMap) {
this->ffzEmotes_.set(std::make_shared<EmoteMap>(std::move(emoteMap)));
});
FfzEmotes::loadChannel(
this->chatroomOwnerId,
[this](auto &&emoteMap) {
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this](auto &&modBadge) {
this->ffzCustomModBadge_.set(std::move(modBadge));
});
}
const QString &ChatroomChannel::getDisplayName() const
File diff suppressed because it is too large Load Diff
+58 -58
View File
@@ -1,58 +1,58 @@
#pragma once
#include <IrcMessage>
#include "messages/Message.hpp"
namespace chatterino {
class TwitchIrcServer;
class Channel;
class IrcMessageHandler
{
IrcMessageHandler() = default;
public:
static IrcMessageHandler &getInstance();
// parseMessage parses a single IRC message into 0+ Chatterino messages
std::vector<MessagePtr> parseMessage(Channel *channel,
Communi::IrcMessage *message);
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage(
Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
// chatterino messages
std::vector<MessagePtr> parseUserNoticeMessage(
Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchIrcServer &server);
void handleModeMessage(Communi::IrcMessage *message);
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
// messages
std::vector<MessagePtr> parseNoticeMessage(
Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server, bool isResub,
bool isAction);
};
} // namespace chatterino
#pragma once
#include <IrcMessage>
#include "messages/Message.hpp"
namespace chatterino {
class TwitchIrcServer;
class Channel;
class IrcMessageHandler
{
IrcMessageHandler() = default;
public:
static IrcMessageHandler &getInstance();
// parseMessage parses a single IRC message into 0+ Chatterino messages
std::vector<MessagePtr> parseMessage(Channel *channel,
Communi::IrcMessage *message);
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
std::vector<MessagePtr> parsePrivMessage(
Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
// parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
// chatterino messages
std::vector<MessagePtr> parseUserNoticeMessage(
Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchIrcServer &server);
void handleModeMessage(Communi::IrcMessage *message);
// parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
// messages
std::vector<MessagePtr> parseNoticeMessage(
Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchIrcServer &server,
bool isResub, bool isAction);
};
} // namespace chatterino
+9 -9
View File
@@ -95,7 +95,7 @@ void TwitchAccount::loadIgnores()
"/blocks");
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onSuccess([=](auto result) -> Outcome {
auto document = result.parseRapidJson();
@@ -168,7 +168,7 @@ void TwitchAccount::ignoreByID(
"/blocks/" + targetUserID);
NetworkRequest(url, NetworkRequestType::Put)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
onFinished(IgnoreResult_Failed,
@@ -245,7 +245,7 @@ void TwitchAccount::unignoreByID(
"/blocks/" + targetUserID);
NetworkRequest(url, NetworkRequestType::Delete)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
onFinished(
@@ -279,7 +279,7 @@ void TwitchAccount::checkFollow(const QString targetUserID,
"/follows/channels/" + targetUserID);
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
if (errorCode == 203)
@@ -308,7 +308,7 @@ void TwitchAccount::followUser(const QString userID,
"/follows/channels/" + userID);
NetworkRequest(requestUrl, NetworkRequestType::Put)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onSuccess([successCallback](auto result) -> Outcome {
// TODO: Properly check result of follow request
@@ -326,7 +326,7 @@ void TwitchAccount::unfollowUser(const QString userID,
"/follows/channels/" + userID);
NetworkRequest(requestUrl, NetworkRequestType::Delete)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([successCallback](int code) {
if (code >= 200 && code <= 299)
@@ -368,7 +368,7 @@ void TwitchAccount::loadEmotes()
"/emotes");
NetworkRequest(url)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccount::loadEmotes] Error {}", errorCode);
@@ -409,7 +409,7 @@ void TwitchAccount::autoModAllow(const QString msgID)
.header("Content-Type", "application/json")
.header("Content-Length", QByteArray::number(qba.size()))
.payload(qba)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccounts::autoModAllow] Error {}", errorCode);
@@ -429,7 +429,7 @@ void TwitchAccount::autoModDeny(const QString msgID)
.header("Content-Type", "application/json")
.header("Content-Length", QByteArray::number(qba.size()))
.payload(qba)
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
.onError([=](int errorCode) {
log("[TwitchAccounts::autoModDeny] Error {}", errorCode);
+234 -234
View File
@@ -1,234 +1,234 @@
#include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
namespace chatterino {
TwitchAccountManager::TwitchAccountManager()
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{
this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect([this](const auto &acc) { //
this->removeUser(acc.item.get());
});
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{
if (!this->currentUser_)
{
return this->anonymousUser_;
}
return this->currentUser_;
}
std::vector<QString> TwitchAccountManager::getUsernames() const
{
std::vector<QString> userNames;
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
userNames.push_back(user->getUserName());
}
return userNames;
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
const QString &username) const
{
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{
return user;
}
}
return nullptr;
}
bool TwitchAccountManager::userExists(const QString &username) const
{
return this->findUserByUsername(username) != nullptr;
}
void TwitchAccountManager::reloadUsers()
{
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
UserData userData;
bool listUpdated = false;
for (const auto &uid : keys)
{
if (uid == "current")
{
continue;
}
auto username = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/username");
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
uid + "/userID");
auto clientID = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/clientID");
auto oauthToken = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/oauthToken");
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
oauthToken.isEmpty())
{
continue;
}
userData.username = username.trimmed();
userData.userID = userID.trimmed();
userData.clientID = clientID.trimmed();
userData.oauthToken = oauthToken.trimmed();
switch (this->addUser(userData))
{
case AddUserResponse::UserAlreadyExists:
{
log("User {} already exists", userData.username);
// Do nothing
}
break;
case AddUserResponse::UserValuesUpdated:
{
log("User {} already exists, and values updated!",
userData.username);
if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect "
"stuff!");
this->currentUserChanged.invoke();
}
}
break;
case AddUserResponse::UserAdded:
{
log("Added user {}", userData.username);
listUpdated = true;
}
break;
}
}
if (listUpdated)
{
this->userListUpdated.invoke();
}
}
void TwitchAccountManager::load()
{
this->reloadUsers();
this->currentUsername.connect([this](const QString &newUsername) {
auto user = this->findUserByUsername(newUsername);
if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
this->currentUser_ = user;
}
else
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
this->currentUser_ = this->anonymousUser_;
}
this->currentUserChanged.invoke();
});
}
bool TwitchAccountManager::isLoggedIn() const
{
if (!this->currentUser_)
{
return false;
}
// Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect
return !this->currentUser_->isAnon();
}
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
auto userID(account->getUserId());
if (!userID.isEmpty())
{
pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID));
}
if (account->getUserName() == this->currentUsername)
{
// The user that was removed is the current user, log into the anonymous
// user
this->currentUsername = "";
}
this->userListUpdated.invoke();
return true;
}
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData)
{
auto previousUser = this->findUserByUsername(userData.username);
if (previousUser)
{
bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID))
{
userUpdated = true;
}
if (previousUser->setOAuthToken(userData.oauthToken))
{
userUpdated = true;
}
if (userUpdated)
{
return AddUserResponse::UserValuesUpdated;
}
else
{
return AddUserResponse::UserAlreadyExists;
}
}
auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}
} // namespace chatterino
#include "providers/twitch/TwitchAccountManager.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
namespace chatterino {
TwitchAccountManager::TwitchAccountManager()
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{
this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent();
currentUser->loadIgnores();
});
this->accounts.itemRemoved.connect([this](const auto &acc) { //
this->removeUser(acc.item.get());
});
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{
if (!this->currentUser_)
{
return this->anonymousUser_;
}
return this->currentUser_;
}
std::vector<QString> TwitchAccountManager::getUsernames() const
{
std::vector<QString> userNames;
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
userNames.push_back(user->getUserName());
}
return userNames;
}
std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
const QString &username) const
{
std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts)
{
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{
return user;
}
}
return nullptr;
}
bool TwitchAccountManager::userExists(const QString &username) const
{
return this->findUserByUsername(username) != nullptr;
}
void TwitchAccountManager::reloadUsers()
{
auto keys = pajlada::Settings::SettingManager::getObjectKeys("/accounts");
UserData userData;
bool listUpdated = false;
for (const auto &uid : keys)
{
if (uid == "current")
{
continue;
}
auto username = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/username");
auto userID = pajlada::Settings::Setting<QString>::get("/accounts/" +
uid + "/userID");
auto clientID = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/clientID");
auto oauthToken = pajlada::Settings::Setting<QString>::get(
"/accounts/" + uid + "/oauthToken");
if (username.isEmpty() || userID.isEmpty() || clientID.isEmpty() ||
oauthToken.isEmpty())
{
continue;
}
userData.username = username.trimmed();
userData.userID = userID.trimmed();
userData.clientID = clientID.trimmed();
userData.oauthToken = oauthToken.trimmed();
switch (this->addUser(userData))
{
case AddUserResponse::UserAlreadyExists:
{
log("User {} already exists", userData.username);
// Do nothing
}
break;
case AddUserResponse::UserValuesUpdated:
{
log("User {} already exists, and values updated!",
userData.username);
if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect "
"stuff!");
this->currentUserChanged.invoke();
}
}
break;
case AddUserResponse::UserAdded:
{
log("Added user {}", userData.username);
listUpdated = true;
}
break;
}
}
if (listUpdated)
{
this->userListUpdated.invoke();
}
}
void TwitchAccountManager::load()
{
this->reloadUsers();
this->currentUsername.connect([this](const QString &newUsername) {
auto user = this->findUserByUsername(newUsername);
if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}",
newUsername);
this->currentUser_ = user;
}
else
{
log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous");
this->currentUser_ = this->anonymousUser_;
}
this->currentUserChanged.invoke();
});
}
bool TwitchAccountManager::isLoggedIn() const
{
if (!this->currentUser_)
{
return false;
}
// Once `TwitchAccount` class has a way to check, we should also return
// false if the credentials are incorrect
return !this->currentUser_->isAnon();
}
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
auto userID(account->getUserId());
if (!userID.isEmpty())
{
pajlada::Settings::SettingManager::removeSetting(
fS("/accounts/uid{}", userID));
}
if (account->getUserName() == this->currentUsername)
{
// The user that was removed is the current user, log into the anonymous
// user
this->currentUsername = "";
}
this->userListUpdated.invoke();
return true;
}
TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData)
{
auto previousUser = this->findUserByUsername(userData.username);
if (previousUser)
{
bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID))
{
userUpdated = true;
}
if (previousUser->setOAuthToken(userData.oauthToken))
{
userUpdated = true;
}
if (userUpdated)
{
return AddUserResponse::UserValuesUpdated;
}
else
{
return AddUserResponse::UserAlreadyExists;
}
}
auto newUser =
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID);
// std::lock_guard<std::mutex> lock(this->mutex);
this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded;
}
} // namespace chatterino
+75 -75
View File
@@ -1,75 +1,75 @@
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/SharedPtrElementLess.hpp"
#include <mutex>
#include <vector>
//
// Warning: This class is not supposed to be created directly.
// Get yourself an instance from our friends over at
// AccountManager.hpp
//
namespace chatterino {
class TwitchAccount;
class AccountController;
class TwitchAccountManager
{
TwitchAccountManager();
public:
struct UserData {
QString username;
QString userID;
QString clientID;
QString oauthToken;
};
// Returns the current twitchUsers, or the anonymous user if we're not
// currently logged in
std::shared_ptr<TwitchAccount> getCurrent();
std::vector<QString> getUsernames() const;
std::shared_ptr<TwitchAccount> findUserByUsername(
const QString &username) const;
bool userExists(const QString &username) const;
void reloadUsers();
void load();
bool isLoggedIn() const;
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>>
accounts;
private:
enum class AddUserResponse {
UserAlreadyExists,
UserValuesUpdated,
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser_;
std::shared_ptr<TwitchAccount> anonymousUser_;
mutable std::mutex mutex_;
friend class AccountController;
};
} // namespace chatterino
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/SharedPtrElementLess.hpp"
#include <mutex>
#include <vector>
//
// Warning: This class is not supposed to be created directly.
// Get yourself an instance from our friends over at
// AccountManager.hpp
//
namespace chatterino {
class TwitchAccount;
class AccountController;
class TwitchAccountManager
{
TwitchAccountManager();
public:
struct UserData {
QString username;
QString userID;
QString clientID;
QString oauthToken;
};
// Returns the current twitchUsers, or the anonymous user if we're not
// currently logged in
std::shared_ptr<TwitchAccount> getCurrent();
std::vector<QString> getUsernames() const;
std::shared_ptr<TwitchAccount> findUserByUsername(
const QString &username) const;
bool userExists(const QString &username) const;
void reloadUsers();
void load();
bool isLoggedIn() const;
pajlada::Settings::Setting<QString> currentUsername{"/accounts/current",
""};
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
SortedSignalVector<std::shared_ptr<TwitchAccount>,
SharedPtrElementLess<TwitchAccount>>
accounts;
private:
enum class AddUserResponse {
UserAlreadyExists,
UserValuesUpdated,
UserAdded,
};
AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser_;
std::shared_ptr<TwitchAccount> anonymousUser_;
mutable std::mutex mutex_;
friend class AccountController;
};
} // namespace chatterino
+2 -2
View File
@@ -16,7 +16,7 @@ void TwitchApi::findUserId(const QString user,
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
NetworkRequest(requestUrl)
.authorizeTwitchV5(getDefaultClientID())
.timeout(30000)
.onSuccess([successCallback](auto result) mutable -> Outcome {
@@ -65,7 +65,7 @@ void TwitchApi::findUserName(const QString userid,
QString requestUrl("https://api.twitch.tv/kraken/users/" + userid);
NetworkRequest(requestUrl)
.authorizeTwitchV5(getDefaultClientID())
.timeout(30000)
.onSuccess([successCallback](auto result) mutable -> Outcome {
+1 -1
View File
@@ -18,7 +18,7 @@ void TwitchBadges::loadTwitchBadges()
"https://badges.twitch.tv/v1/badges/global/display?language=en");
NetworkRequest(url)
.onSuccess([this](auto result) -> Outcome {
auto root = result.parseJson();
auto badgeSets = this->badgeSets_.access();
+49 -20
View File
@@ -88,7 +88,6 @@ TwitchChannel::TwitchChannel(const QString &name,
, globalFfz_(ffz)
, bttvEmotes_(std::make_shared<EmoteMap>())
, ffzEmotes_(std::make_shared<EmoteMap>())
, ffzCustomModBadge_(name)
, mod_(false)
{
log("[TwitchChannel:{}] Opened", name);
@@ -139,7 +138,6 @@ void TwitchChannel::initialize()
{
this->refreshChatters();
this->refreshBadges();
this->ffzCustomModBadge_.loadCustomModBadge();
}
bool TwitchChannel::isEmpty() const
@@ -165,10 +163,17 @@ void TwitchChannel::refreshBTTVChannelEmotes()
void TwitchChannel::refreshFFZChannelEmotes()
{
FfzEmotes::loadChannel(
this->roomId(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
this->roomId(),
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
if (auto shared = weak.lock())
this->ffzEmotes_.set(
std::make_shared<EmoteMap>(std::move(emoteMap)));
},
[this, weak = weakOf<Channel>(this)](auto &&modBadge) {
if (auto shared = weak.lock())
{
this->ffzCustomModBadge_.set(std::move(modBadge));
}
});
}
@@ -685,22 +690,23 @@ void TwitchChannel::refreshBadges()
void TwitchChannel::refreshCheerEmotes()
{
/*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->getRoomId()};
auto request = NetworkRequest::twitchRequest(url.string);
request.setCaller(QThread::currentThread());
request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
QString url("https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->roomId());
NetworkRequest::twitchRequest(url)
.onSuccess([this,
weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
std::vector<CheerEmoteSet> emoteSets;
for (auto &set : cheerEmoteSets) {
for (auto &set : cheerEmoteSets)
{
auto cheerEmoteSet = CheerEmoteSet();
cheerEmoteSet.regex = QRegularExpression(
"^" + set.prefix.toLower() + "([1-9][0-9]*)$");
"^" + set.prefix + "([1-9][0-9]*)$",
QRegularExpression::CaseInsensitiveOption);
for (auto &tier : set.tiers) {
for (auto &tier : set.tiers)
{
CheerEmote cheerEmote;
cheerEmote.color = QColor(tier.color);
@@ -733,7 +739,7 @@ void TwitchChannel::refreshCheerEmotes()
std::sort(cheerEmoteSet.cheerEmotes.begin(),
cheerEmoteSet.cheerEmotes.end(),
[](const auto &lhs, const auto &rhs) {
return lhs.minBits < rhs.minBits; //
return lhs.minBits > rhs.minBits;
});
emoteSets.emplace_back(cheerEmoteSet);
@@ -741,10 +747,8 @@ void TwitchChannel::refreshCheerEmotes()
*this->cheerEmoteSets_.access() = std::move(emoteSets);
return Success;
});
request.execute();
*/
})
.execute();
}
boost::optional<EmotePtr> TwitchChannel::twitchBadge(
@@ -765,9 +769,34 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
boost::optional<EmotePtr> TwitchChannel::ffzCustomModBadge() const
{
if (auto badge = this->ffzCustomModBadge_.badge())
return badge;
return this->ffzCustomModBadge_.get();
}
boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
{
auto sets = this->cheerEmoteSets_.access();
for (const auto &set : *sets)
{
auto match = set.regex.match(string);
if (!match.hasMatch())
{
continue;
}
QString amount = match.captured(1);
bool ok = false;
int bitAmount = amount.toInt(&ok);
if (!ok)
{
log("Error parsing bit amount in cheerEmote");
}
for (const auto &emote : set.cheerEmotes)
{
if (bitAmount >= emote.minBits)
{
return emote;
}
}
}
return boost::none;
}
+5 -2
View File
@@ -6,7 +6,7 @@
#include "common/ChannelChatters.hpp"
#include "common/Outcome.hpp"
#include "common/UniqueAccess.hpp"
#include "providers/ffz/FfzModBadge.hpp"
#include "common/UsernameSet.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
#include <rapidjson/document.h>
@@ -96,6 +96,9 @@ public:
boost::optional<EmotePtr> twitchBadge(const QString &set,
const QString &version) const;
// Cheers
boost::optional<CheerEmote> cheerEmote(const QString &string);
// Signals
pajlada::Signals::NoArgSignal roomIdChanged;
pajlada::Signals::NoArgSignal userStateChanged;
@@ -145,13 +148,13 @@ protected:
FfzEmotes &globalFfz_;
Atomic<std::shared_ptr<const EmoteMap>> bttvEmotes_;
Atomic<std::shared_ptr<const EmoteMap>> ffzEmotes_;
Atomic<boost::optional<EmotePtr>> ffzCustomModBadge_;
private:
// Badges
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>>
badgeSets_; // "subscribers": { "0": ... "3": ... "6": ...
UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
FfzModBadge ffzCustomModBadge_;
bool mod_ = false;
bool vip_ = false;
+373 -373
View File
@@ -1,373 +1,373 @@
#include "TwitchIrcServer.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/PostToThread.hpp"
#include <IrcCommand>
#include <cassert>
// using namespace Communi;
using namespace std::chrono_literals;
namespace chatterino {
namespace {
bool isChatroom(const QString &channel)
{
if (channel.left(10) == "chatrooms:")
{
auto reflist = channel.splitRef(':');
if (reflist.size() == 3)
{
return true;
}
}
return false;
}
} // namespace
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{
this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
// this->connect(); },
// this->signalHolder_,
// false);
}
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
{
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { postToThread([this] { this->connect(); }); });
this->twitchBadges.loadTwitchBadges();
this->bttv.loadEmotes();
this->ffz.loadEmotes();
}
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
std::shared_ptr<TwitchAccount> account =
getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
QString username = account->getUserName();
QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:"))
{
oauthToken.prepend("oauth:");
}
connection->setUserName(username);
connection->setNickName(username);
connection->setRealName(username);
if (!account->isAnon())
{
connection->setPassword(oauthToken);
}
connection->setSecure(true);
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
// SSL disabled: irc://irc.chat.twitch.tv:6667
// SSL enabled: irc://irc.chat.twitch.tv:6697
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6697);
this->open(type);
}
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
const QString &channelName)
{
std::shared_ptr<TwitchChannel> channel;
if (isChatroom(channelName))
{
channel = std::static_pointer_cast<TwitchChannel>(
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
channelName, this->twitchBadges, this->bttv, this->ffz)));
}
else
{
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
channelName, this->twitchBadges, this->bttv, this->ffz));
}
channel->initialize();
channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent);
});
return std::shared_ptr<Channel>(channel);
}
void TwitchIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
void TwitchIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
if (message->type() == Communi::IrcMessage::Type::Private)
{
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/membership CAP REQ
if (command == "MODE")
{
handler.handleModeMessage(message);
}
else if (command == "JOIN")
{
handler.handleJoinMessage(message);
}
else if (command == "PART")
{
handler.handlePartMessage(message);
}
}
void TwitchIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/commands CAP REQ
if (command == "USERSTATE")
{
handler.handleUserStateMessage(message);
}
else if (command == "WHISPER")
{
handler.handleWhisperMessage(message);
}
else if (command == "USERNOTICE")
{
handler.handleUserNoticeMessage(message, *this);
}
else if (command == "ROOMSTATE")
{
handler.handleRoomStateMessage(message);
}
else if (command == "CLEARCHAT")
{
handler.handleClearChatMessage(message);
}
else if (command == "CLEARMSG")
{
handler.handleClearMessageMessage(message);
}
else if (command == "NOTICE")
{
handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message));
}
}
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
{
AbstractIrcServer::onReadConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
}
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
{
AbstractIrcServer::onWriteConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
}
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
const QString &channelName)
{
if (channelName == "/whispers")
{
return this->whispersChannel;
}
if (channelName == "/mentions")
{
return this->mentionsChannel;
}
if (channelName == "$$$")
{
static auto channel =
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++)
{
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
}
auto timer = new QTimer;
QObject::connect(timer, &QTimer::timeout, [] {
channel->addMessage(
makeSystemMessage(QTime::currentTime().toString()));
});
timer->start(500);
return timer;
}();
return channel;
}
return nullptr;
}
void TwitchIrcServer::forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func)
{
this->forEachChannel(func);
func(this->whispersChannel);
func(this->mentionsChannel);
}
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
const QString &channelId)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels)
{
auto channel = weakChannel.lock();
if (!channel)
continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel)
continue;
if (twitchChannel->roomId() == channelId &&
twitchChannel->getName().splitRef(":").size() < 3)
{
return twitchChannel;
}
}
return Channel::getEmpty();
}
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1).toLower();
else
return dirtyChannelName.toLower();
}
bool TwitchIrcServer::hasSeparateWriteConnection() const
{
return true;
// return getSettings()->twitchSeperateWriteConnection;
}
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
const QString &message, bool &sent)
{
sent = false;
{
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
// std::queue<std::chrono::steady_clock::time_point>
auto &lastMessage = channel->hasHighRateLimit()
? this->lastMessageMod_
: this->lastMessagePleb_;
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
{
if (this->lastErrorTimeSpeed_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending messages too fast");
channel->addMessage(errorMessage);
this->lastErrorTimeSpeed_ = now;
}
return;
}
// remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{
lastMessage.pop();
}
// check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount)
{
if (this->lastErrorTimeAmount_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending too many messages");
channel->addMessage(errorMessage);
this->lastErrorTimeAmount_ = now;
}
return;
}
lastMessage.push(now);
}
this->sendMessage(channel->getName(), message);
sent = true;
}
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
{
return this->bttv;
}
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
{
return this->ffz;
}
} // namespace chatterino
#include "TwitchIrcServer.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchHelpers.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/PostToThread.hpp"
#include <IrcCommand>
#include <cassert>
// using namespace Communi;
using namespace std::chrono_literals;
namespace chatterino {
namespace {
bool isChatroom(const QString &channel)
{
if (channel.left(10) == "chatrooms:")
{
auto reflist = channel.splitRef(':');
if (reflist.size() == 3)
{
return true;
}
}
return false;
}
} // namespace
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{
this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
// this->connect(); },
// this->signalHolder_,
// false);
}
void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
{
getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { postToThread([this] { this->connect(); }); });
this->twitchBadges.loadTwitchBadges();
this->bttv.loadEmotes();
this->ffz.loadEmotes();
}
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
std::shared_ptr<TwitchAccount> account =
getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName();
QString username = account->getUserName();
QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:"))
{
oauthToken.prepend("oauth:");
}
connection->setUserName(username);
connection->setNickName(username);
connection->setRealName(username);
if (!account->isAnon())
{
connection->setPassword(oauthToken);
}
connection->setSecure(true);
// https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
// SSL disabled: irc://irc.chat.twitch.tv:6667
// SSL enabled: irc://irc.chat.twitch.tv:6697
connection->setHost("irc.chat.twitch.tv");
connection->setPort(6697);
this->open(type);
}
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
const QString &channelName)
{
std::shared_ptr<TwitchChannel> channel;
if (isChatroom(channelName))
{
channel = std::static_pointer_cast<TwitchChannel>(
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
channelName, this->twitchBadges, this->bttv, this->ffz)));
}
else
{
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
channelName, this->twitchBadges, this->bttv, this->ffz));
}
channel->initialize();
channel->sendMessageSignal.connect(
[this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
this->onMessageSendRequested(channel, msg, sent);
});
return std::shared_ptr<Channel>(channel);
}
void TwitchIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
}
void TwitchIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
if (message->type() == Communi::IrcMessage::Type::Private)
{
// We already have a handler for private messages
return;
}
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/membership CAP REQ
if (command == "MODE")
{
handler.handleModeMessage(message);
}
else if (command == "JOIN")
{
handler.handleJoinMessage(message);
}
else if (command == "PART")
{
handler.handlePartMessage(message);
}
}
void TwitchIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
// Below commands enabled through the twitch.tv/commands CAP REQ
if (command == "USERSTATE")
{
handler.handleUserStateMessage(message);
}
else if (command == "WHISPER")
{
handler.handleWhisperMessage(message);
}
else if (command == "USERNOTICE")
{
handler.handleUserNoticeMessage(message, *this);
}
else if (command == "ROOMSTATE")
{
handler.handleRoomStateMessage(message);
}
else if (command == "CLEARCHAT")
{
handler.handleClearChatMessage(message);
}
else if (command == "CLEARMSG")
{
handler.handleClearMessageMessage(message);
}
else if (command == "NOTICE")
{
handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message));
}
}
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
{
AbstractIrcServer::onReadConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/membership enables the JOIN/PART/MODE/NAMES commands. See https://dev.twitch.tv/docs/irc/membership/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/membership");
}
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
{
AbstractIrcServer::onWriteConnected(connection);
// twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags/
// twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands/
connection->sendRaw("CAP REQ :twitch.tv/tags twitch.tv/commands");
}
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
const QString &channelName)
{
if (channelName == "/whispers")
{
return this->whispersChannel;
}
if (channelName == "/mentions")
{
return this->mentionsChannel;
}
if (channelName == "$$$")
{
static auto channel =
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++)
{
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
}
auto timer = new QTimer;
QObject::connect(timer, &QTimer::timeout, [] {
channel->addMessage(
makeSystemMessage(QTime::currentTime().toString()));
});
timer->start(500);
return timer;
}();
return channel;
}
return nullptr;
}
void TwitchIrcServer::forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func)
{
this->forEachChannel(func);
func(this->whispersChannel);
func(this->mentionsChannel);
}
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
const QString &channelId)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels)
{
auto channel = weakChannel.lock();
if (!channel)
continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel)
continue;
if (twitchChannel->roomId() == channelId &&
twitchChannel->getName().splitRef(":").size() < 3)
{
return twitchChannel;
}
}
return Channel::getEmpty();
}
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1).toLower();
else
return dirtyChannelName.toLower();
}
bool TwitchIrcServer::hasSeparateWriteConnection() const
{
return true;
// return getSettings()->twitchSeperateWriteConnection;
}
void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
const QString &message, bool &sent)
{
sent = false;
{
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
// std::queue<std::chrono::steady_clock::time_point>
auto &lastMessage = channel->hasHighRateLimit()
? this->lastMessageMod_
: this->lastMessagePleb_;
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
{
if (this->lastErrorTimeSpeed_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending messages too fast");
channel->addMessage(errorMessage);
this->lastErrorTimeSpeed_ = now;
}
return;
}
// remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{
lastMessage.pop();
}
// check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount)
{
if (this->lastErrorTimeAmount_ + 30s < now)
{
auto errorMessage =
makeSystemMessage("sending too many messages");
channel->addMessage(errorMessage);
this->lastErrorTimeAmount_ = now;
}
return;
}
lastMessage.push(now);
}
this->sendMessage(channel->getName(), message);
sent = true;
}
const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
{
return this->bttv;
}
const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
{
return this->ffz;
}
} // namespace chatterino
+85 -85
View File
@@ -1,85 +1,85 @@
#pragma once
#include "common/Atomic.hpp"
#include "common/Channel.hpp"
#include "common/Singleton.hpp"
#include "pajlada/signals/signalholder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include <chrono>
#include <memory>
#include <queue>
namespace chatterino {
class Settings;
class Paths;
class PubSub;
class TwitchChannel;
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
{
public:
TwitchIrcServer();
virtual ~TwitchIrcServer() override = default;
virtual void initialize(Settings &settings, Paths &paths) override;
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
PubSub *pubsub;
const BttvEmotes &getBttvEmotes() const;
const FfzEmotes &getFfzEmotes() const;
protected:
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) override;
virtual void privateMessageReceived(
Communi::IrcPrivateMessage *message) override;
virtual void readConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void writeConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void onReadConnected(IrcConnection *connection) override;
virtual void onWriteConnected(IrcConnection *connection) override;
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelname) override;
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
virtual bool hasSeparateWriteConnection() const override;
private:
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
bool &sent);
std::mutex lastMessageMutex_;
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
TwitchBadges twitchBadges;
BttvEmotes bttv;
FfzEmotes ffz;
pajlada::Signals::SignalHolder signalHolder_;
};
} // namespace chatterino
#pragma once
#include "common/Atomic.hpp"
#include "common/Channel.hpp"
#include "common/Singleton.hpp"
#include "pajlada/signals/signalholder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include <chrono>
#include <memory>
#include <queue>
namespace chatterino {
class Settings;
class Paths;
class PubSub;
class TwitchChannel;
class TwitchIrcServer final : public AbstractIrcServer, public Singleton
{
public:
TwitchIrcServer();
virtual ~TwitchIrcServer() override = default;
virtual void initialize(Settings &settings, Paths &paths) override;
void forEachChannelAndSpecialChannels(std::function<void(ChannelPtr)> func);
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
Atomic<QString> lastUserThatWhisperedMe;
const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel;
PubSub *pubsub;
const BttvEmotes &getBttvEmotes() const;
const FfzEmotes &getFfzEmotes() const;
protected:
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) override;
virtual void privateMessageReceived(
Communi::IrcPrivateMessage *message) override;
virtual void readConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void writeConnectionMessageReceived(
Communi::IrcMessage *message) override;
virtual void onReadConnected(IrcConnection *connection) override;
virtual void onWriteConnected(IrcConnection *connection) override;
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelname) override;
virtual QString cleanChannelName(const QString &dirtyChannelName) override;
virtual bool hasSeparateWriteConnection() const override;
private:
void onMessageSendRequested(TwitchChannel *channel, const QString &message,
bool &sent);
std::mutex lastMessageMutex_;
std::queue<std::chrono::steady_clock::time_point> lastMessagePleb_;
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
TwitchBadges twitchBadges;
BttvEmotes bttv;
FfzEmotes ffz;
pajlada::Signals::SignalHolder signalHolder_;
};
} // namespace chatterino
+66 -68
View File
@@ -143,9 +143,21 @@ bool TwitchMessageBuilder::isIgnored() const
return false;
}
inline QMediaPlayer *getPlayer()
{
if (isGuiThread())
{
static auto player = new QMediaPlayer;
return player;
}
else
{
return nullptr;
}
}
void TwitchMessageBuilder::triggerHighlights()
{
static auto player = new QMediaPlayer;
static QUrl currentPlayerUrl;
if (this->historicalMessage_)
@@ -165,21 +177,24 @@ void TwitchMessageBuilder::triggerHighlights()
if (this->highlightSound_ && resolveFocus)
{
// update the media player url if necessary
QUrl highlightSoundUrl =
getSettings()->customHighlightSound
? QUrl::fromLocalFile(
getSettings()->pathHighlightSound.getValue())
: QUrl("qrc:/sounds/ping2.wav");
if (currentPlayerUrl != highlightSoundUrl)
if (auto player = getPlayer())
{
player->setMedia(highlightSoundUrl);
// update the media player url if necessary
QUrl highlightSoundUrl =
getSettings()->customHighlightSound
? QUrl::fromLocalFile(
getSettings()->pathHighlightSound.getValue())
: QUrl("qrc:/sounds/ping2.wav");
currentPlayerUrl = highlightSoundUrl;
if (currentPlayerUrl != highlightSoundUrl)
{
player->setMedia(highlightSoundUrl);
currentPlayerUrl = highlightSoundUrl;
}
player->play();
}
player->play();
}
if (this->highlightAlert_)
@@ -274,7 +289,7 @@ MessagePtr TwitchMessageBuilder::build()
if (iterator != this->tags.end())
{
this->hasBits_ = true;
// bits = iterator.value().toString();
this->bits = iterator.value().toString();
}
// twitch emotes
@@ -340,7 +355,7 @@ void TwitchMessageBuilder::addWords(
auto i = int();
auto currentTwitchEmote = twitchEmotes.begin();
for (const auto &word : words)
for (auto word : words)
{
// check if it's a twitch emote twitch emote
while (currentTwitchEmote != twitchEmotes.end() &&
@@ -361,9 +376,19 @@ void TwitchMessageBuilder::addWords(
MessageElementFlag::TwitchEmote);
i += word.length() + 1;
int len = std::get<2>(*currentTwitchEmote).string.length();
currentTwitchEmote++;
continue;
if (len < word.length())
{
word = word.mid(len);
this->message().elements.back()->setTrailingSpace(false);
}
else
{
continue;
}
}
// split words
@@ -952,7 +977,7 @@ void TwitchMessageBuilder::parseHighlights()
{
HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false);
getSettings()->enableSelfHighlightSound, false, false);
activeHighlights.emplace_back(std::move(selfHighlight));
}
@@ -1171,7 +1196,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
{
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
{
this->emplace<BadgeElement>(
this->emplace<ModBadgeElement>(
customModBadge.get(),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string);
@@ -1285,56 +1310,29 @@ void TwitchMessageBuilder::appendChatterinoBadges()
}
}
Outcome TwitchMessageBuilder::tryParseCheermote(const QString & /*string*/)
Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
{
// auto app = getApp();
//// Try to parse custom cheermotes
// const auto &channelResources = app->resources->channels[this->roomID_];
// if (channelResources.loaded) {
// for (const auto &cheermoteSet : channelResources.cheermoteSets) {
// auto match = cheermoteSet.regex.match(string);
// if (!match.hasMatch()) {
// continue;
// }
// QString amount = match.captured(1);
// bool ok = false;
// int numBits = amount.toInt(&ok);
// if (!ok) {
// Log("Error parsing bit amount in tryParseCheermote");
// return Failure;
// }
// auto savedIt = cheermoteSet.cheermotes.end();
// // Fetch cheermote that matches our numBits
// for (auto it = cheermoteSet.cheermotes.begin(); it !=
// cheermoteSet.cheermotes.end();
// ++it) {
// if (numBits >= it->minBits) {
// savedIt = it;
// } else {
// break;
// }
// }
// if (savedIt == cheermoteSet.cheermotes.end()) {
// Log("Error getting a cheermote from a cheermote set for the
// bit amount {}",
// numBits);
// return Failure;
// }
// const auto &cheermote = *savedIt;
// this->emplace<EmoteElement>(cheermote.animatedEmote,
// MessageElementFlag::BitsAnimated);
// this->emplace<TextElement>(amount, MessageElementFlag::Text,
// cheermote.color);
// return Success;
// }
//}
return Failure;
auto cheerOpt = this->twitchChannel->cheerEmote(string);
if (!cheerOpt)
{
return Failure;
}
auto &cheerEmote = *cheerOpt;
if (cheerEmote.staticEmote)
{
this->emplace<EmoteElement>(cheerEmote.staticEmote,
MessageElementFlag::BitsStatic);
}
if (cheerEmote.animatedEmote)
{
this->emplace<EmoteElement>(cheerEmote.animatedEmote,
MessageElementFlag::BitsAnimated);
}
if (cheerEmote.color != QColor())
{
this->emplace<TextElement>(this->bits, MessageElementFlag::BitsAmount,
cheerEmote.color);
}
return Success;
}
} // namespace chatterino
@@ -78,6 +78,7 @@ private:
QString roomID_;
bool hasBits_ = false;
QString bits;
bool historicalMessage_ = false;
QString userId_;