Added support for Twitch's Chat Replies (#3722)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel Sage
2022-07-31 06:45:25 -04:00
committed by GitHub
parent a280089693
commit 20c974fdab
53 changed files with 2022 additions and 310 deletions
+146 -42
View File
@@ -1,6 +1,5 @@
#include "providers/twitch/TwitchChannel.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
@@ -182,12 +181,34 @@ TwitchChannel::TwitchChannel(const QString &name)
this->refreshBTTVChannelEmotes(false);
});
this->messageRemovedFromStart.connect([this](MessagePtr &msg) {
if (msg->replyThread)
{
if (msg->replyThread->liveCount(msg) == 0)
{
this->threads_.erase(msg->replyThread->rootId());
}
}
});
// timers
QObject::connect(&this->chattersListTimer_, &QTimer::timeout, [=] {
this->refreshChatters();
});
this->chattersListTimer_.start(5 * 60 * 1000);
QObject::connect(&this->threadClearTimer_, &QTimer::timeout, [=] {
// We periodically check for any dangling reply threads that missed
// being cleaned up on messageRemovedFromStart. This could occur if
// some other part of the program, like a user card, held a reference
// to the message.
//
// It seems difficult to actually replicate a situation where things
// are actually cleaned up, but I've verified that cleanups DO happen.
this->cleanUpReplyThreads();
});
this->threadClearTimer_.start(5 * 60 * 1000);
// debugging
#if 0
for (int i = 0; i < 1000; i++) {
@@ -311,46 +332,34 @@ boost::optional<ChannelPointReward> TwitchChannel::channelPointReward(
return it->second;
}
void TwitchChannel::sendMessage(const QString &message)
void TwitchChannel::showLoginMessage()
{
const auto linkColor = MessageColor(MessageColor::Link);
const auto accountsLink = Link(Link::OpenAccountsPage, QString());
const auto currentUser = getApp()->accounts->twitch.getCurrent();
const auto expirationText =
QStringLiteral("You need to log in to send messages. You can link your "
"Twitch account");
const auto loginPromptText = QStringLiteral("in the settings.");
auto builder = MessageBuilder();
builder.message().flags.set(MessageFlag::System);
builder.message().flags.set(MessageFlag::DoNotTriggerNotification);
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(expirationText, MessageElementFlag::Text,
MessageColor::System);
builder
.emplace<TextElement>(loginPromptText, MessageElementFlag::Text,
linkColor)
->setLink(accountsLink);
this->addMessage(builder.release());
}
QString TwitchChannel::prepareMessage(const QString &message) const
{
auto app = getApp();
if (!app->accounts->twitch.isLoggedIn())
{
if (message.isEmpty())
{
return;
}
const auto linkColor = MessageColor(MessageColor::Link);
const auto accountsLink = Link(Link::OpenAccountsPage, QString());
const auto currentUser = getApp()->accounts->twitch.getCurrent();
const auto expirationText =
QString("You need to log in to send messages. You can link your "
"Twitch account");
const auto loginPromptText = QString("in the settings.");
auto builder = MessageBuilder();
builder.message().flags.set(MessageFlag::System);
builder.message().flags.set(MessageFlag::DoNotTriggerNotification);
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(expirationText, MessageElementFlag::Text,
MessageColor::System);
builder
.emplace<TextElement>(loginPromptText, MessageElementFlag::Text,
linkColor)
->setLink(accountsLink);
this->addMessage(builder.release());
return;
}
qCDebug(chatterinoTwitch)
<< "[TwitchChannel" << this->getName() << "] Send message:" << message;
// Do last message processing
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
// This is to make sure that combined emoji go through properly, see
@@ -361,7 +370,7 @@ void TwitchChannel::sendMessage(const QString &message)
if (parsedMessage.isEmpty())
{
return;
return "";
}
if (!this->hasHighRateLimit())
@@ -395,6 +404,32 @@ void TwitchChannel::sendMessage(const QString &message)
}
}
return parsedMessage;
}
void TwitchChannel::sendMessage(const QString &message)
{
auto app = getApp();
if (!app->accounts->twitch.isLoggedIn())
{
if (!message.isEmpty())
{
this->showLoginMessage();
}
return;
}
qCDebug(chatterinoTwitch)
<< "[TwitchChannel" << this->getName() << "] Send message:" << message;
// Do last message processing
QString parsedMessage = this->prepareMessage(message);
if (parsedMessage.isEmpty())
{
return;
}
bool messageSent = false;
this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent);
@@ -405,6 +440,40 @@ void TwitchChannel::sendMessage(const QString &message)
}
}
void TwitchChannel::sendReply(const QString &message, const QString &replyId)
{
auto app = getApp();
if (!app->accounts->twitch.isLoggedIn())
{
if (!message.isEmpty())
{
this->showLoginMessage();
}
return;
}
qCDebug(chatterinoTwitch) << "[TwitchChannel" << this->getName()
<< "] Send reply message:" << message;
// Do last message processing
QString parsedMessage = this->prepareMessage(message);
if (parsedMessage.isEmpty())
{
return;
}
bool messageSent = false;
this->sendReplySignal.invoke(this->getName(), parsedMessage, replyId,
messageSent);
if (messageSent)
{
qCDebug(chatterinoTwitch) << "sent";
this->lastSentMessage_ = parsedMessage;
}
}
bool TwitchChannel::isMod() const
{
return this->mod_;
@@ -794,8 +863,10 @@ void TwitchChannel::loadRecentMessages()
}
}
for (auto builtMessage :
handler.parseMessage(shared.get(), message))
auto builtMessages = handler.parseMessageWithReply(
shared.get(), message, allBuiltMessages);
for (auto builtMessage : builtMessages)
{
builtMessage->flags.set(MessageFlag::RecentMessage);
allBuiltMessages.emplace_back(builtMessage);
@@ -922,6 +993,39 @@ void TwitchChannel::fetchDisplayName()
[] {});
}
void TwitchChannel::addReplyThread(const std::shared_ptr<MessageThread> &thread)
{
this->threads_[thread->rootId()] = thread;
}
const std::unordered_map<QString, std::weak_ptr<MessageThread>>
&TwitchChannel::threads() const
{
return this->threads_;
}
void TwitchChannel::cleanUpReplyThreads()
{
for (auto it = this->threads_.begin(), last = this->threads_.end();
it != last;)
{
bool doErase = true;
if (auto thread = it->second.lock())
{
doErase = thread->liveCount() == 0;
}
if (doErase)
{
it = this->threads_.erase(it);
}
else
{
++it;
}
}
}
void TwitchChannel::refreshBadges()
{
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" +