2024-01-06 13:18:37 +01:00
|
|
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "Application.hpp"
|
2023-10-13 17:41:23 +02:00
|
|
|
#include "common/Channel.hpp"
|
2024-08-18 14:04:26 +02:00
|
|
|
#include "common/Common.hpp"
|
2019-12-21 10:11:23 +01:00
|
|
|
#include "common/Env.hpp"
|
2020-11-21 16:20:10 +01:00
|
|
|
#include "common/QLogging.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "controllers/accounts/AccountController.hpp"
|
2024-08-18 14:04:26 +02:00
|
|
|
#include "messages/LimitedQueueSnapshot.hpp"
|
2018-11-03 21:26:57 +01:00
|
|
|
#include "messages/Message.hpp"
|
|
|
|
#include "messages/MessageBuilder.hpp"
|
2024-01-21 14:20:21 +01:00
|
|
|
#include "providers/bttv/BttvEmotes.hpp"
|
|
|
|
#include "providers/ffz/FfzEmotes.hpp"
|
2024-08-18 14:04:26 +02:00
|
|
|
#include "providers/irc/IrcConnection2.hpp"
|
2024-01-21 14:20:21 +01:00
|
|
|
#include "providers/seventv/SeventvEmotes.hpp"
|
2022-11-13 12:07:41 +01:00
|
|
|
#include "providers/seventv/SeventvEventAPI.hpp"
|
2022-12-31 15:41:01 +01:00
|
|
|
#include "providers/twitch/api/Helix.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
|
|
|
#include "providers/twitch/TwitchAccount.hpp"
|
2018-08-11 22:23:06 +02:00
|
|
|
#include "providers/twitch/TwitchChannel.hpp"
|
2022-11-13 12:07:41 +01:00
|
|
|
#include "singletons/Settings.hpp"
|
2018-06-26 14:09:39 +02:00
|
|
|
#include "util/PostToThread.hpp"
|
2024-08-18 14:04:26 +02:00
|
|
|
#include "util/RatelimitBucket.hpp"
|
2019-09-08 22:27:57 +02:00
|
|
|
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include <IrcCommand>
|
2024-08-18 14:04:26 +02:00
|
|
|
#include <IrcMessage>
|
|
|
|
#include <pajlada/signals/signal.hpp>
|
|
|
|
#include <pajlada/signals/signalholder.hpp>
|
|
|
|
#include <QCoreApplication>
|
2021-05-08 15:57:00 +02:00
|
|
|
#include <QMetaEnum>
|
|
|
|
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
#include <cassert>
|
2024-08-18 14:04:26 +02:00
|
|
|
#include <functional>
|
|
|
|
#include <mutex>
|
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
|
|
|
|
2018-06-24 12:59:47 +02:00
|
|
|
using namespace std::chrono_literals;
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-11-13 12:07:41 +01:00
|
|
|
namespace {
|
|
|
|
|
2024-08-18 14:04:26 +02:00
|
|
|
// Ratelimits for joinBucket_
|
|
|
|
constexpr int JOIN_RATELIMIT_BUDGET = 18;
|
|
|
|
constexpr int JOIN_RATELIMIT_COOLDOWN = 12500;
|
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
using namespace chatterino;
|
|
|
|
|
|
|
|
void sendHelixMessage(const std::shared_ptr<TwitchChannel> &channel,
|
|
|
|
const QString &message, const QString &replyParentId = {})
|
|
|
|
{
|
2024-03-30 11:24:09 +01:00
|
|
|
auto broadcasterID = channel->roomId();
|
|
|
|
if (broadcasterID.isEmpty())
|
|
|
|
{
|
2024-07-07 22:03:05 +02:00
|
|
|
channel->addSystemMessage(
|
|
|
|
"Sending messages in this channel isn't possible.");
|
2024-03-30 11:24:09 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
getHelix()->sendChatMessage(
|
|
|
|
{
|
2024-03-30 11:24:09 +01:00
|
|
|
.broadcasterID = broadcasterID,
|
2024-02-25 14:45:55 +01:00
|
|
|
.senderID =
|
2024-07-21 15:09:59 +02:00
|
|
|
getApp()->getAccounts()->twitch.getCurrent()->getUserId(),
|
2024-02-25 14:45:55 +01:00
|
|
|
.message = message,
|
|
|
|
.replyParentMessageID = replyParentId,
|
|
|
|
},
|
|
|
|
[weak = std::weak_ptr(channel)](const auto &res) {
|
|
|
|
auto chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res.isSent)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-07 22:03:05 +02:00
|
|
|
if (res.dropReason)
|
|
|
|
{
|
|
|
|
chan->addSystemMessage(res.dropReason->message);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chan->addSystemMessage("Your message was not sent.");
|
|
|
|
}
|
2024-02-25 14:45:55 +01:00
|
|
|
},
|
2024-03-30 11:24:09 +01:00
|
|
|
[weak = std::weak_ptr(channel)](auto error, auto message) {
|
2024-02-25 14:45:55 +01:00
|
|
|
auto chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-30 11:24:09 +01:00
|
|
|
if (message.isEmpty())
|
|
|
|
{
|
|
|
|
message = "(empty message)";
|
|
|
|
}
|
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
using Error = decltype(error);
|
|
|
|
|
|
|
|
auto errorMessage = [&]() -> QString {
|
|
|
|
switch (error)
|
|
|
|
{
|
|
|
|
case Error::MissingText:
|
|
|
|
return "You can't send an empty message.";
|
|
|
|
case Error::BadRequest:
|
|
|
|
return "Failed to send message: " + message;
|
|
|
|
case Error::Forbidden:
|
|
|
|
return "You are not allowed to send messages in this "
|
|
|
|
"channel.";
|
|
|
|
case Error::MessageTooLarge:
|
|
|
|
return "Your message was too long.";
|
|
|
|
case Error::UserMissingScope:
|
|
|
|
return "Missing required scope. Re-login with your "
|
|
|
|
"account and try again.";
|
|
|
|
case Error::Forwarded:
|
|
|
|
return message;
|
|
|
|
case Error::Unknown:
|
|
|
|
default:
|
|
|
|
return "Unknown error: " + message;
|
|
|
|
}
|
|
|
|
}();
|
2024-07-07 22:03:05 +02:00
|
|
|
chan->addSystemMessage(errorMessage);
|
2024-02-25 14:45:55 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if chat messages should be sent over Helix
|
|
|
|
bool shouldSendHelixChat()
|
|
|
|
{
|
|
|
|
switch (getSettings()->chatSendProtocol)
|
|
|
|
{
|
|
|
|
case ChatSendProtocol::Helix:
|
|
|
|
return true;
|
|
|
|
case ChatSendProtocol::Default:
|
|
|
|
case ChatSendProtocol::IRC:
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
assert(false && "Invalid chat protocol value");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-13 12:07:41 +01:00
|
|
|
} // namespace
|
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
namespace chatterino {
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
TwitchIrcServer::TwitchIrcServer()
|
2018-07-06 17:30:12 +02:00
|
|
|
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
|
|
|
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
2021-05-09 16:17:04 +02:00
|
|
|
, liveChannel(new Channel("/live", Channel::Type::TwitchLive))
|
2023-12-03 23:07:30 +01:00
|
|
|
, automodChannel(new Channel("/automod", Channel::Type::TwitchAutomod))
|
2022-09-03 18:12:44 +02:00
|
|
|
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2024-08-18 14:04:26 +02:00
|
|
|
// Initialize the connections
|
|
|
|
// XXX: don't create write connection if there is no separate write connection.
|
|
|
|
this->writeConnection_.reset(new IrcConnection);
|
|
|
|
this->writeConnection_->moveToThread(
|
|
|
|
QCoreApplication::instance()->thread());
|
|
|
|
|
|
|
|
// Apply a leaky bucket rate limiting to JOIN messages
|
|
|
|
auto actuallyJoin = [&](QString message) {
|
|
|
|
if (!this->channels.contains(message))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this->readConnection_->sendRaw("JOIN #" + message);
|
|
|
|
};
|
|
|
|
this->joinBucket_.reset(new RatelimitBucket(
|
|
|
|
JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this));
|
|
|
|
|
|
|
|
QObject::connect(this->writeConnection_.get(),
|
|
|
|
&Communi::IrcConnection::messageReceived, this,
|
|
|
|
[this](auto msg) {
|
|
|
|
this->writeConnectionMessageReceived(msg);
|
|
|
|
});
|
|
|
|
QObject::connect(this->writeConnection_.get(),
|
|
|
|
&Communi::IrcConnection::connected, this, [this] {
|
|
|
|
this->onWriteConnected(this->writeConnection_.get());
|
|
|
|
});
|
|
|
|
this->connections_.managedConnect(
|
|
|
|
this->writeConnection_->connectionLost, [this](bool timeout) {
|
|
|
|
qCDebug(chatterinoIrc)
|
|
|
|
<< "Write connection reconnect requested. Timeout:" << timeout;
|
|
|
|
this->writeConnection_->smartReconnect();
|
|
|
|
});
|
2020-03-01 12:05:08 +01:00
|
|
|
|
2024-08-18 14:04:26 +02:00
|
|
|
// Listen to read connection message signals
|
|
|
|
this->readConnection_.reset(new IrcConnection);
|
|
|
|
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
|
|
|
|
|
|
|
QObject::connect(this->readConnection_.get(),
|
|
|
|
&Communi::IrcConnection::messageReceived, this,
|
|
|
|
[this](auto msg) {
|
|
|
|
this->readConnectionMessageReceived(msg);
|
|
|
|
});
|
|
|
|
QObject::connect(this->readConnection_.get(),
|
|
|
|
&Communi::IrcConnection::privateMessageReceived, this,
|
|
|
|
[this](auto msg) {
|
|
|
|
this->privateMessageReceived(msg);
|
|
|
|
});
|
|
|
|
QObject::connect(this->readConnection_.get(),
|
|
|
|
&Communi::IrcConnection::connected, this, [this] {
|
|
|
|
this->onReadConnected(this->readConnection_.get());
|
|
|
|
});
|
|
|
|
QObject::connect(this->readConnection_.get(),
|
|
|
|
&Communi::IrcConnection::disconnected, this, [this] {
|
|
|
|
this->onDisconnected();
|
|
|
|
});
|
|
|
|
this->connections_.managedConnect(
|
|
|
|
this->readConnection_->connectionLost, [this](bool timeout) {
|
|
|
|
qCDebug(chatterinoIrc)
|
|
|
|
<< "Read connection reconnect requested. Timeout:" << timeout;
|
|
|
|
if (timeout)
|
|
|
|
{
|
|
|
|
// Show additional message since this is going to interrupt a
|
|
|
|
// connection that is still "connected"
|
|
|
|
this->addGlobalSystemMessage(
|
|
|
|
"Server connection timed out, reconnecting");
|
|
|
|
}
|
|
|
|
this->readConnection_->smartReconnect();
|
|
|
|
});
|
|
|
|
this->connections_.managedConnect(this->readConnection_->heartbeat, [this] {
|
|
|
|
this->markChannelsConnected();
|
|
|
|
});
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2024-06-03 10:31:30 +02:00
|
|
|
void TwitchIrcServer::initialize()
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2024-07-21 15:09:59 +02:00
|
|
|
getApp()->getAccounts()->twitch.currentUserChanged.connect([this]() {
|
2020-11-08 12:02:19 +01:00
|
|
|
postToThread([this] {
|
|
|
|
this->connect();
|
|
|
|
});
|
|
|
|
});
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
|
|
|
ConnectionType type)
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2018-05-26 20:26:25 +02:00
|
|
|
std::shared_ptr<TwitchAccount> account =
|
2024-07-21 15:09:59 +02:00
|
|
|
getApp()->getAccounts()->twitch.getCurrent();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2020-11-21 16:20:10 +01:00
|
|
|
qCDebug(chatterinoTwitch) << "logging in as" << account->getUserName();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2021-07-17 17:18:17 +02:00
|
|
|
// 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
|
|
|
|
// twitch.tv/membership enables the JOIN/PART/NAMES commands. See https://dev.twitch.tv/docs/irc/membership
|
|
|
|
// This is enabled so we receive USERSTATE messages when joining channels / typing messages, along with the other command capabilities
|
|
|
|
QStringList caps{"twitch.tv/tags", "twitch.tv/commands"};
|
|
|
|
if (type != ConnectionType::Write)
|
|
|
|
{
|
|
|
|
caps.push_back("twitch.tv/membership");
|
|
|
|
}
|
|
|
|
|
|
|
|
connection->network()->setSkipCapabilityValidation(true);
|
|
|
|
connection->network()->setRequestedCapabilities(caps);
|
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
QString username = account->getUserName();
|
|
|
|
QString oauthToken = account->getOAuthToken();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
if (!oauthToken.startsWith("oauth:"))
|
|
|
|
{
|
|
|
|
oauthToken.prepend("oauth:");
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
connection->setUserName(username);
|
|
|
|
connection->setNickName(username);
|
|
|
|
connection->setRealName(username);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
if (!account->isAnon())
|
|
|
|
{
|
|
|
|
connection->setPassword(oauthToken);
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2023-01-08 12:45:24 +01:00
|
|
|
// https://dev.twitch.tv/docs/irc#connecting-to-the-twitch-irc-server
|
2019-10-04 13:06:15 +02:00
|
|
|
// SSL disabled: irc://irc.chat.twitch.tv:6667 (or port 80)
|
|
|
|
// SSL enabled: irc://irc.chat.twitch.tv:6697 (or port 443)
|
2019-12-21 10:11:23 +01:00
|
|
|
connection->setHost(Env::get().twitchServerHost);
|
|
|
|
connection->setPort(Env::get().twitchServerPort);
|
|
|
|
connection->setSecure(Env::get().twitchServerSecure);
|
2019-09-18 13:03:16 +02:00
|
|
|
|
|
|
|
this->open(type);
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
|
|
|
const QString &channelName)
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2023-09-16 13:52:51 +02:00
|
|
|
auto channel = std::make_shared<TwitchChannel>(channelName);
|
2018-08-13 13:54:39 +02:00
|
|
|
channel->initialize();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2023-09-16 13:52:51 +02:00
|
|
|
// We can safely ignore these signal connections since the TwitchIrcServer is only
|
|
|
|
// ever destroyed when the full Application state is about to be destroyed, at which point
|
|
|
|
// no Channel's should live
|
|
|
|
// NOTE: CHANNEL_LIFETIME
|
|
|
|
std::ignore = channel->sendMessageSignal.connect(
|
2024-02-25 14:45:55 +01:00
|
|
|
[this, channel = std::weak_ptr(channel)](auto &chan, auto &msg,
|
|
|
|
bool &sent) {
|
|
|
|
auto c = channel.lock();
|
|
|
|
if (!c)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this->onMessageSendRequested(c, msg, sent);
|
2018-06-06 18:57:22 +02:00
|
|
|
});
|
2023-09-16 13:52:51 +02:00
|
|
|
std::ignore = channel->sendReplySignal.connect(
|
2024-02-25 14:45:55 +01:00
|
|
|
[this, channel = std::weak_ptr(channel)](auto &chan, auto &msg,
|
|
|
|
auto &replyId, bool &sent) {
|
|
|
|
auto c = channel.lock();
|
|
|
|
if (!c)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this->onReplySendRequested(c, msg, replyId, sent);
|
2022-07-31 12:45:25 +02:00
|
|
|
});
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2023-09-16 13:52:51 +02:00
|
|
|
return channel;
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
void TwitchIrcServer::privateMessageReceived(
|
|
|
|
Communi::IrcPrivateMessage *message)
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2024-08-18 14:04:26 +02:00
|
|
|
IrcMessageHandler::instance().handlePrivMessage(message, *this);
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
void TwitchIrcServer::readConnectionMessageReceived(
|
|
|
|
Communi::IrcMessage *message)
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2018-06-04 21:05:18 +02:00
|
|
|
if (message->type() == Communi::IrcMessage::Type::Private)
|
|
|
|
{
|
2018-02-05 15:11:50 +01:00
|
|
|
// We already have a handler for private messages
|
|
|
|
return;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
const QString &command = message->command();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-10-07 22:42:34 +02:00
|
|
|
auto &handler = IrcMessageHandler::instance();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-08-31 17:41:49 +02:00
|
|
|
// Below commands enabled through the twitch.tv/membership CAP REQ
|
2021-07-17 15:09:21 +02:00
|
|
|
if (command == "JOIN")
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2019-08-31 17:41:49 +02:00
|
|
|
handler.handleJoinMessage(message);
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-08-31 17:41:49 +02:00
|
|
|
else if (command == "PART")
|
2019-04-19 22:44:02 +02:00
|
|
|
{
|
2019-08-31 17:41:49 +02:00
|
|
|
handler.handlePartMessage(message);
|
2019-04-19 22:44:02 +02:00
|
|
|
}
|
2019-09-21 11:38:08 +02:00
|
|
|
else if (command == "USERSTATE")
|
|
|
|
{
|
|
|
|
// Received USERSTATE upon JOINing a channel
|
|
|
|
handler.handleUserStateMessage(message);
|
|
|
|
}
|
2019-09-21 11:45:47 +02:00
|
|
|
else if (command == "ROOMSTATE")
|
|
|
|
{
|
|
|
|
// Received ROOMSTATE upon JOINing a channel
|
|
|
|
handler.handleRoomStateMessage(message);
|
|
|
|
}
|
2019-09-21 11:54:30 +02:00
|
|
|
else if (command == "CLEARCHAT")
|
|
|
|
{
|
|
|
|
handler.handleClearChatMessage(message);
|
|
|
|
}
|
2019-09-21 11:56:37 +02:00
|
|
|
else if (command == "CLEARMSG")
|
|
|
|
{
|
|
|
|
handler.handleClearMessageMessage(message);
|
|
|
|
}
|
|
|
|
else if (command == "USERNOTICE")
|
|
|
|
{
|
2024-08-18 14:04:26 +02:00
|
|
|
handler.handleUserNoticeMessage(message, *this);
|
2019-09-21 11:56:37 +02:00
|
|
|
}
|
|
|
|
else if (command == "NOTICE")
|
|
|
|
{
|
|
|
|
handler.handleNoticeMessage(
|
|
|
|
static_cast<Communi::IrcNoticeMessage *>(message));
|
|
|
|
}
|
|
|
|
else if (command == "WHISPER")
|
|
|
|
{
|
|
|
|
handler.handleWhisperMessage(message);
|
|
|
|
}
|
2021-01-09 18:05:02 +01:00
|
|
|
else if (command == "RECONNECT")
|
|
|
|
{
|
|
|
|
this->addGlobalSystemMessage(
|
|
|
|
"Twitch Servers requested us to reconnect, reconnecting");
|
2023-12-16 13:38:35 +01:00
|
|
|
this->markChannelsConnected();
|
2021-01-09 18:05:02 +01:00
|
|
|
this->connect();
|
|
|
|
}
|
2021-07-17 17:18:17 +02:00
|
|
|
else if (command == "GLOBALUSERSTATE")
|
|
|
|
{
|
|
|
|
handler.handleGlobalUserStateMessage(message);
|
|
|
|
}
|
2019-08-31 17:41:49 +02:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
void TwitchIrcServer::writeConnectionMessageReceived(
|
|
|
|
Communi::IrcMessage *message)
|
2019-08-31 17:41:49 +02:00
|
|
|
{
|
|
|
|
const QString &command = message->command();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-10-07 22:42:34 +02:00
|
|
|
auto &handler = IrcMessageHandler::instance();
|
2019-08-31 17:41:49 +02:00
|
|
|
// Below commands enabled through the twitch.tv/commands CAP REQ
|
|
|
|
if (command == "USERSTATE")
|
2018-02-05 15:11:50 +01:00
|
|
|
{
|
2021-08-03 17:55:04 +02:00
|
|
|
// Received USERSTATE upon sending PRIVMSG messages
|
2018-05-26 18:06:55 +02:00
|
|
|
handler.handleUserStateMessage(message);
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-22 16:16:08 +02:00
|
|
|
else if (command == "NOTICE")
|
|
|
|
{
|
2021-08-03 17:55:04 +02:00
|
|
|
// List of expected NOTICE messages on write connection
|
|
|
|
// https://git.kotmisia.pl/Mm2PL/docs/src/branch/master/irc_msg_ids.md#command-results
|
2019-09-22 16:16:08 +02:00
|
|
|
handler.handleNoticeMessage(
|
|
|
|
static_cast<Communi::IrcNoticeMessage *>(message));
|
|
|
|
}
|
2021-01-09 18:05:02 +01:00
|
|
|
else if (command == "RECONNECT")
|
|
|
|
{
|
|
|
|
this->addGlobalSystemMessage(
|
|
|
|
"Twitch Servers requested us to reconnect, reconnecting");
|
|
|
|
this->connect();
|
|
|
|
}
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2024-08-18 14:04:26 +02:00
|
|
|
void TwitchIrcServer::onReadConnected(IrcConnection *connection)
|
|
|
|
{
|
|
|
|
(void)connection;
|
|
|
|
|
|
|
|
std::lock_guard lock(this->channelMutex);
|
|
|
|
|
|
|
|
// join channels
|
|
|
|
for (auto &&weak : this->channels)
|
|
|
|
{
|
|
|
|
if (auto channel = weak.lock())
|
|
|
|
{
|
|
|
|
this->joinBucket_->send(channel->getName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// connected/disconnected message
|
|
|
|
auto connectedMsg = makeSystemMessage("connected");
|
|
|
|
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
|
|
|
auto reconnected = makeSystemMessage("reconnected");
|
|
|
|
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
|
|
|
|
|
|
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
|
|
{
|
|
|
|
std::shared_ptr<Channel> chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
|
|
|
|
|
|
|
bool replaceMessage =
|
|
|
|
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
|
|
|
|
MessageFlag::DisconnectedMessage);
|
|
|
|
|
|
|
|
if (replaceMessage)
|
|
|
|
{
|
|
|
|
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chan->addMessage(connectedMsg, MessageContext::Original);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this->falloffCounter_ = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::onWriteConnected(IrcConnection *connection)
|
|
|
|
{
|
|
|
|
(void)connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::onDisconnected()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
MessageBuilder b(systemMessage, "disconnected");
|
|
|
|
b->flags.set(MessageFlag::DisconnectedMessage);
|
|
|
|
auto disconnectedMsg = b.release();
|
|
|
|
|
|
|
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
|
|
{
|
|
|
|
std::shared_ptr<Channel> chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->addMessage(disconnectedMsg, MessageContext::Original);
|
|
|
|
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
|
|
{
|
|
|
|
channel->markDisconnected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
|
2018-02-05 15:11:50 +01:00
|
|
|
const QString &channelName)
|
|
|
|
{
|
|
|
|
if (channelName == "/whispers")
|
|
|
|
{
|
2018-06-24 15:14:05 +02:00
|
|
|
return this->whispersChannel;
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
if (channelName == "/mentions")
|
|
|
|
{
|
2018-06-24 15:14:05 +02:00
|
|
|
return this->mentionsChannel;
|
2018-02-05 15:11:50 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2021-05-09 16:17:04 +02:00
|
|
|
if (channelName == "/live")
|
|
|
|
{
|
|
|
|
return this->liveChannel;
|
|
|
|
}
|
|
|
|
|
2023-12-03 23:07:30 +01:00
|
|
|
if (channelName == "/automod")
|
|
|
|
{
|
|
|
|
return this->automodChannel;
|
|
|
|
}
|
|
|
|
|
2023-05-27 13:33:01 +02:00
|
|
|
static auto getTimer = [](ChannelPtr channel, int msBetweenMessages,
|
|
|
|
bool addInitialMessages) {
|
|
|
|
if (addInitialMessages)
|
|
|
|
{
|
2018-12-04 08:56:07 +01:00
|
|
|
for (auto i = 0; i < 1000; i++)
|
|
|
|
{
|
2024-07-07 22:03:05 +02:00
|
|
|
channel->addSystemMessage(QString::number(i + 1));
|
2018-12-04 08:56:07 +01:00
|
|
|
}
|
2023-05-27 13:33:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
auto *timer = new QTimer;
|
|
|
|
QObject::connect(timer, &QTimer::timeout, [channel] {
|
2024-07-07 22:03:05 +02:00
|
|
|
channel->addSystemMessage(QTime::currentTime().toString());
|
2023-05-27 13:33:01 +02:00
|
|
|
});
|
|
|
|
timer->start(msBetweenMessages);
|
|
|
|
return timer;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (channelName == "$$$")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 500, true);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$:e")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 500, false);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 250, true);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$:e")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 250, false);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 100, true);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$:e")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 100, false);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2023-05-27 13:33:01 +02:00
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$$")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 50, true);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$$:e")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 50, false);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$$$")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 25, true);
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
if (channelName == "$$$$$$$:e")
|
|
|
|
{
|
|
|
|
static auto channel = std::make_shared<Channel>(
|
|
|
|
channelName, chatterino::Channel::Type::Misc);
|
|
|
|
getTimer(channel, 25, false);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-11-03 21:26:57 +01:00
|
|
|
return channel;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
return nullptr;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
void TwitchIrcServer::forEachChannelAndSpecialChannels(
|
2018-02-05 21:20:38 +01:00
|
|
|
std::function<void(ChannelPtr)> func)
|
|
|
|
{
|
2018-06-22 23:24:45 +02:00
|
|
|
this->forEachChannel(func);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-02-05 21:20:38 +01:00
|
|
|
func(this->whispersChannel);
|
|
|
|
func(this->mentionsChannel);
|
2021-05-09 16:17:04 +02:00
|
|
|
func(this->liveChannel);
|
2023-12-03 23:07:30 +01:00
|
|
|
func(this->automodChannel);
|
2018-02-05 21:20:38 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
|
2018-07-15 20:28:54 +02:00
|
|
|
const QString &channelId)
|
2018-04-22 15:36:01 +02:00
|
|
|
{
|
2018-06-22 23:28:20 +02:00
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-06-22 23:28:20 +02:00
|
|
|
for (const auto &weakChannel : this->channels)
|
|
|
|
{
|
|
|
|
auto channel = weakChannel.lock();
|
2018-07-14 14:24:18 +02:00
|
|
|
if (!channel)
|
2024-01-14 17:54:52 +01:00
|
|
|
{
|
2018-07-14 14:24:18 +02:00
|
|
|
continue;
|
2024-01-14 17:54:52 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-06-22 23:28:20 +02:00
|
|
|
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
|
2018-07-14 14:24:18 +02:00
|
|
|
if (!twitchChannel)
|
2024-01-14 17:54:52 +01:00
|
|
|
{
|
2018-07-14 14:24:18 +02:00
|
|
|
continue;
|
2024-01-14 17:54:52 +01:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-05-01 21:46:53 +02:00
|
|
|
if (twitchChannel->roomId() == channelId &&
|
2023-02-19 20:19:18 +01:00
|
|
|
twitchChannel->getName().count(':') < 2)
|
2018-08-11 17:15:17 +02:00
|
|
|
{
|
2018-06-22 23:28:20 +02:00
|
|
|
return twitchChannel;
|
2018-04-22 15:36:01 +02:00
|
|
|
}
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2018-04-22 15:36:01 +02:00
|
|
|
return Channel::getEmpty();
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2019-09-18 13:03:16 +02:00
|
|
|
QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
2018-04-01 15:10:15 +02:00
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
if (dirtyChannelName.startsWith('#'))
|
2024-01-14 17:54:52 +01:00
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
return dirtyChannelName.mid(1).toLower();
|
2024-01-14 17:54:52 +01:00
|
|
|
}
|
2019-09-18 13:03:16 +02:00
|
|
|
else
|
2024-01-14 17:54:52 +01:00
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
return dirtyChannelName.toLower();
|
2024-01-14 17:54:52 +01:00
|
|
|
}
|
2018-04-01 15:10:15 +02:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
bool TwitchIrcServer::prepareToSend(
|
|
|
|
const std::shared_ptr<TwitchChannel> &channel)
|
2018-06-24 12:59:47 +02:00
|
|
|
{
|
2022-07-31 12:45:25 +02:00
|
|
|
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
auto &lastMessage = channel->hasHighRateLimit() ? this->lastMessageMod_
|
|
|
|
: this->lastMessagePleb_;
|
|
|
|
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
|
|
|
|
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
auto now = std::chrono::steady_clock::now();
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
// check if you are sending messages too fast
|
|
|
|
if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
|
|
|
|
{
|
|
|
|
if (this->lastErrorTimeSpeed_ + 30s < now)
|
2018-06-24 12:59:47 +02:00
|
|
|
{
|
2024-07-07 22:03:05 +02:00
|
|
|
channel->addSystemMessage("You are sending messages too quickly.");
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
this->lastErrorTimeSpeed_ = now;
|
2018-06-24 12:59:47 +02:00
|
|
|
}
|
2022-07-31 12:45:25 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
// remove messages older than 30 seconds
|
|
|
|
while (!lastMessage.empty() && lastMessage.front() + 32s < now)
|
|
|
|
{
|
|
|
|
lastMessage.pop();
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
// check if you are sending too many messages
|
|
|
|
if (lastMessage.size() >= maxMessageCount)
|
|
|
|
{
|
|
|
|
if (this->lastErrorTimeAmount_ + 30s < now)
|
2018-06-24 12:59:47 +02:00
|
|
|
{
|
2024-07-07 22:03:05 +02:00
|
|
|
channel->addSystemMessage("You are sending too many messages.");
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
this->lastErrorTimeAmount_ = now;
|
2018-06-24 12:59:47 +02:00
|
|
|
}
|
2022-07-31 12:45:25 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2022-07-31 12:45:25 +02:00
|
|
|
lastMessage.push(now);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
void TwitchIrcServer::onMessageSendRequested(
|
|
|
|
const std::shared_ptr<TwitchChannel> &channel, const QString &message,
|
|
|
|
bool &sent)
|
2022-07-31 12:45:25 +02:00
|
|
|
{
|
|
|
|
sent = false;
|
|
|
|
|
|
|
|
bool canSend = this->prepareToSend(channel);
|
|
|
|
if (!canSend)
|
|
|
|
{
|
|
|
|
return;
|
2018-06-24 12:59:47 +02:00
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
if (shouldSendHelixChat())
|
|
|
|
{
|
|
|
|
sendHelixMessage(channel, message);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->sendMessage(channel->getName(), message);
|
|
|
|
}
|
|
|
|
|
2018-06-24 12:59:47 +02:00
|
|
|
sent = true;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
void TwitchIrcServer::onReplySendRequested(
|
|
|
|
const std::shared_ptr<TwitchChannel> &channel, const QString &message,
|
|
|
|
const QString &replyId, bool &sent)
|
2022-07-31 12:45:25 +02:00
|
|
|
{
|
|
|
|
sent = false;
|
|
|
|
|
|
|
|
bool canSend = this->prepareToSend(channel);
|
|
|
|
if (!canSend)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-25 14:45:55 +01:00
|
|
|
if (shouldSendHelixChat())
|
|
|
|
{
|
|
|
|
sendHelixMessage(channel, message, replyId);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->sendRawMessage("@reply-parent-msg-id=" + replyId + " PRIVMSG #" +
|
|
|
|
channel->getName() + " :" + message);
|
|
|
|
}
|
2022-07-31 12:45:25 +02:00
|
|
|
sent = true;
|
|
|
|
}
|
|
|
|
|
2023-10-13 17:41:23 +02:00
|
|
|
const IndirectChannel &TwitchIrcServer::getWatchingChannel() const
|
|
|
|
{
|
|
|
|
return this->watchingChannel;
|
|
|
|
}
|
|
|
|
|
2024-06-01 14:56:40 +02:00
|
|
|
void TwitchIrcServer::setWatchingChannel(ChannelPtr newWatchingChannel)
|
|
|
|
{
|
|
|
|
this->watchingChannel.reset(newWatchingChannel);
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getWhispersChannel() const
|
|
|
|
{
|
|
|
|
return this->whispersChannel;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getMentionsChannel() const
|
|
|
|
{
|
|
|
|
return this->mentionsChannel;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getLiveChannel() const
|
|
|
|
{
|
|
|
|
return this->liveChannel;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getAutomodChannel() const
|
|
|
|
{
|
|
|
|
return this->automodChannel;
|
|
|
|
}
|
|
|
|
|
2024-02-17 13:26:54 +01:00
|
|
|
QString TwitchIrcServer::getLastUserThatWhisperedMe() const
|
|
|
|
{
|
|
|
|
return this->lastUserThatWhisperedMe.get();
|
|
|
|
}
|
|
|
|
|
2024-06-01 14:56:40 +02:00
|
|
|
void TwitchIrcServer::setLastUserThatWhisperedMe(const QString &user)
|
|
|
|
{
|
|
|
|
this->lastUserThatWhisperedMe.set(user);
|
|
|
|
}
|
|
|
|
|
2022-08-28 12:20:47 +02:00
|
|
|
void TwitchIrcServer::reloadAllBTTVChannelEmotes()
|
|
|
|
{
|
|
|
|
this->forEachChannel([](const auto &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
|
|
{
|
|
|
|
channel->refreshBTTVChannelEmotes(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::reloadAllFFZChannelEmotes()
|
|
|
|
{
|
|
|
|
this->forEachChannel([](const auto &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
|
|
{
|
|
|
|
channel->refreshFFZChannelEmotes(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-10-16 13:22:17 +02:00
|
|
|
|
|
|
|
void TwitchIrcServer::reloadAllSevenTVChannelEmotes()
|
|
|
|
{
|
|
|
|
this->forEachChannel([](const auto &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
|
|
{
|
|
|
|
channel->refreshSevenTVChannelEmotes(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-11-13 12:07:41 +01:00
|
|
|
|
|
|
|
void TwitchIrcServer::forEachSeventvEmoteSet(
|
|
|
|
const QString &emoteSetId, std::function<void(TwitchChannel &)> func)
|
|
|
|
{
|
|
|
|
this->forEachChannel([emoteSetId, func](const auto &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
|
|
|
|
channel->seventvEmoteSetID() == emoteSetId)
|
|
|
|
{
|
|
|
|
func(*channel);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
void TwitchIrcServer::forEachSeventvUser(
|
|
|
|
const QString &userId, std::function<void(TwitchChannel &)> func)
|
|
|
|
{
|
|
|
|
this->forEachChannel([userId, func](const auto &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
|
|
|
|
channel->seventvUserID() == userId)
|
|
|
|
{
|
|
|
|
func(*channel);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::dropSeventvChannel(const QString &userID,
|
|
|
|
const QString &emoteSetID)
|
|
|
|
{
|
2024-08-08 15:08:31 +02:00
|
|
|
if (!getApp()->getSeventvEventAPI())
|
2022-11-13 12:07:41 +01:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
// ignore empty values
|
|
|
|
bool skipUser = userID.isEmpty();
|
|
|
|
bool skipSet = emoteSetID.isEmpty();
|
|
|
|
|
|
|
|
bool foundUser = skipUser;
|
|
|
|
bool foundSet = skipSet;
|
|
|
|
for (std::weak_ptr<Channel> &weak : this->channels)
|
|
|
|
{
|
|
|
|
ChannelPtr chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
|
|
|
|
if (!foundSet && channel->seventvEmoteSetID() == emoteSetID)
|
|
|
|
{
|
|
|
|
foundSet = true;
|
|
|
|
}
|
|
|
|
if (!foundUser && channel->seventvUserID() == userID)
|
|
|
|
{
|
|
|
|
foundUser = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (foundSet && foundUser)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!foundUser)
|
|
|
|
{
|
2024-08-08 15:08:31 +02:00
|
|
|
getApp()->getSeventvEventAPI()->unsubscribeUser(userID);
|
2022-11-13 12:07:41 +01:00
|
|
|
}
|
|
|
|
if (!foundSet)
|
|
|
|
{
|
2024-08-08 15:08:31 +02:00
|
|
|
getApp()->getSeventvEventAPI()->unsubscribeEmoteSet(emoteSetID);
|
2022-11-13 12:07:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-18 14:04:26 +02:00
|
|
|
void TwitchIrcServer::markChannelsConnected()
|
|
|
|
{
|
|
|
|
this->forEachChannel([](const ChannelPtr &chan) {
|
|
|
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
|
|
{
|
|
|
|
channel->markConnected();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::addFakeMessage(const QString &data)
|
|
|
|
{
|
|
|
|
auto *fakeMessage = Communi::IrcMessage::fromData(
|
|
|
|
data.toUtf8(), this->readConnection_.get());
|
|
|
|
|
|
|
|
if (fakeMessage->command() == "PRIVMSG")
|
|
|
|
{
|
|
|
|
this->privateMessageReceived(
|
|
|
|
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->readConnectionMessageReceived(fakeMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::addGlobalSystemMessage(const QString &messageText)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
MessageBuilder b(systemMessage, messageText);
|
|
|
|
auto message = b.release();
|
|
|
|
|
|
|
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
|
|
{
|
|
|
|
std::shared_ptr<Channel> chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->addMessage(message, MessageContext::Original);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
|
|
|
{
|
|
|
|
ChannelPtr chan = weak.lock();
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
func(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::connect()
|
|
|
|
{
|
|
|
|
this->disconnect();
|
|
|
|
|
|
|
|
this->initializeConnection(this->writeConnection_.get(),
|
|
|
|
ConnectionType::Write);
|
|
|
|
this->initializeConnection(this->readConnection_.get(),
|
|
|
|
ConnectionType::Read);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::disconnect()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
|
|
|
|
|
|
|
this->readConnection_->close();
|
|
|
|
this->writeConnection_->close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::sendMessage(const QString &channelName,
|
|
|
|
const QString &message)
|
|
|
|
{
|
|
|
|
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::sendRawMessage(const QString &rawMessage)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> locker(this->connectionMutex_);
|
|
|
|
|
|
|
|
this->writeConnection_->sendRaw(rawMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
|
|
|
{
|
|
|
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
|
|
|
|
|
|
|
// try get channel
|
|
|
|
ChannelPtr chan = this->getChannelOrEmpty(channelName);
|
|
|
|
if (chan != Channel::getEmpty())
|
|
|
|
{
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
// value doesn't exist
|
|
|
|
chan = this->createChannel(channelName);
|
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
return Channel::getEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
this->channels.insert(channelName, chan);
|
|
|
|
this->connections_.managedConnect(chan->destroyed, [this, channelName] {
|
|
|
|
// fourtf: issues when the server itself is destroyed
|
|
|
|
|
|
|
|
qCDebug(chatterinoIrc) << "[TwitchIrcServer::addChannel]" << channelName
|
|
|
|
<< "was destroyed";
|
|
|
|
this->channels.remove(channelName);
|
|
|
|
|
|
|
|
if (this->readConnection_)
|
|
|
|
{
|
|
|
|
this->readConnection_->sendRaw("PART #" + channelName);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// join IRC channel
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
|
|
|
|
|
|
|
|
if (this->readConnection_)
|
|
|
|
{
|
|
|
|
if (this->readConnection_->isConnected())
|
|
|
|
{
|
|
|
|
this->joinBucket_->send(channelName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChannelPtr TwitchIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
|
|
|
|
{
|
|
|
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
|
|
|
|
|
|
|
// try get special channel
|
|
|
|
ChannelPtr chan = this->getCustomChannel(channelName);
|
|
|
|
if (chan)
|
|
|
|
{
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
// value exists
|
|
|
|
auto it = this->channels.find(channelName);
|
|
|
|
if (it != this->channels.end())
|
|
|
|
{
|
|
|
|
chan = it.value().lock();
|
|
|
|
|
|
|
|
if (chan)
|
|
|
|
{
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Channel::getEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TwitchIrcServer::open(ConnectionType type)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(this->connectionMutex_);
|
|
|
|
|
|
|
|
if (type == ConnectionType::Write)
|
|
|
|
{
|
|
|
|
this->writeConnection_->open();
|
|
|
|
}
|
|
|
|
if (type == ConnectionType::Read)
|
|
|
|
{
|
|
|
|
this->readConnection_->open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-05 15:11:50 +01:00
|
|
|
} // namespace chatterino
|