Add subtitle to Hype Chats (#4715)

* feat: more hype chat

* Add `std::chrono::seconds` overload to formatTime

* Move & rename it to HypeChat + some other mini things

* Add changelog entry

* fix formattime test

---------

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
nerix
2023-07-30 18:54:42 +02:00
committed by GitHub
parent 71594ad0d8
commit 703847c9ba
8 changed files with 207 additions and 8 deletions
+43 -7
View File
@@ -1,6 +1,7 @@
#include "IrcMessageHandler.hpp"
#include "Application.hpp"
#include "common/Literals.hpp"
#include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "messages/LimitedQueue.hpp"
@@ -26,6 +27,8 @@
#include "util/StreamerMode.hpp"
#include <IrcMessage>
#include <QLocale>
#include <QStringBuilder>
#include <memory>
#include <unordered_set>
@@ -156,9 +159,23 @@ void updateReplyParticipatedStatus(const QVariantMap &tags,
}
}
ChannelPtr channelOrEmptyByTarget(const QString &target,
TwitchIrcServer &server)
{
QString channelName;
if (!trimChannelName(target, channelName))
{
return Channel::getEmpty();
}
return server.getChannelOrEmpty(channelName);
}
} // namespace
namespace chatterino {
using namespace literals;
static float relativeSimilarity(const QString &str1, const QString &str2)
{
// Longest Common Substring Problem
@@ -314,6 +331,16 @@ std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
builtMessages.emplace_back(builder.build());
builder.triggerHighlights();
}
if (message->tags().contains(u"pinned-chat-paid-amount"_s))
{
auto ptr = TwitchMessageBuilder::buildHypeChatMessage(message);
if (ptr)
{
builtMessages.emplace_back(std::move(ptr));
}
}
return builtMessages;
}
@@ -330,6 +357,21 @@ void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
message, message->target(),
message->content().replace(COMBINED_FIXER, ZERO_WIDTH_JOINER), server,
false, message->isAction());
auto chan = channelOrEmptyByTarget(message->target(), server);
if (chan->isEmpty())
{
return;
}
if (message->tags().contains(u"pinned-chat-paid-amount"_s))
{
auto ptr = TwitchMessageBuilder::buildHypeChatMessage(message);
if (ptr)
{
chan->addMessage(ptr);
}
}
}
std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
@@ -442,13 +484,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
TwitchIrcServer &server, bool isSub,
bool isAction)
{
QString channelName;
if (!trimChannelName(target, channelName))
{
return;
}
auto chan = server.getChannelOrEmpty(channelName);
auto chan = channelOrEmptyByTarget(target, server);
if (chan->isEmpty())
{
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "common/LinkParser.hpp"
#include "common/Literals.hpp"
#include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/ignores/IgnoreController.hpp"
@@ -28,8 +29,10 @@
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/FormatTime.hpp"
#include "util/Helpers.hpp"
#include "util/IrcHelpers.hpp"
#include "util/QStringHash.hpp"
#include "util/Qt.hpp"
#include "widgets/Window.hpp"
@@ -38,8 +41,15 @@
#include <QDebug>
#include <QStringRef>
#include <chrono>
#include <unordered_set>
using namespace chatterino::literals;
namespace {
using namespace std::chrono_literals;
const QString regexHelpString("(\\w+)[.,!?;:]*?$");
// matches a mention with punctuation at the end, like "@username," or "@username!!!" where capture group would return "username"
@@ -53,6 +63,19 @@ const QSet<QString> zeroWidthEmotes{
"ReinDeer", "CandyCane", "cvMask", "cvHazmat",
};
struct HypeChatPaidLevel {
std::chrono::seconds duration;
uint8_t numeric;
};
const std::unordered_map<QString, HypeChatPaidLevel> HYPE_CHAT_PAID_LEVEL{
{u"ONE"_s, {30s, 1}}, {u"TWO"_s, {2min + 30s, 2}},
{u"THREE"_s, {5min, 3}}, {u"FOUR"_s, {10min, 4}},
{u"FIVE"_s, {30min, 5}}, {u"SIX"_s, {1h, 6}},
{u"SEVEN"_s, {2h, 7}}, {u"EIGHT"_s, {3h, 8}},
{u"NINE"_s, {4h, 9}}, {u"TEN"_s, {5h, 10}},
};
} // namespace
namespace chatterino {
@@ -1739,6 +1762,45 @@ void TwitchMessageBuilder::listOfUsersSystemMessage(
builder->message().searchText = text;
}
MessagePtr TwitchMessageBuilder::buildHypeChatMessage(
Communi::IrcPrivateMessage *message)
{
auto level = message->tag(u"pinned-chat-paid-level"_s).toString();
auto currency = message->tag(u"pinned-chat-paid-currency"_s).toString();
bool okAmount = false;
auto amount = message->tag(u"pinned-chat-paid-amount"_s).toInt(&okAmount);
bool okExponent = false;
auto exponent =
message->tag(u"pinned-chat-paid-exponent"_s).toInt(&okExponent);
if (!okAmount || !okExponent || currency.isEmpty())
{
return {};
}
// additionally, there's `pinned-chat-paid-is-system-message` which isn't used by Chatterino.
QString subtitle;
auto levelIt = HYPE_CHAT_PAID_LEVEL.find(level);
if (levelIt != HYPE_CHAT_PAID_LEVEL.end())
{
const auto &level = levelIt->second;
subtitle = u"Level %1 Hype Chat (%2) "_s.arg(level.numeric)
.arg(formatTime(level.duration));
}
else
{
subtitle = u"Hype Chat "_s;
}
// actualAmount = amount * 10^(-exponent)
double actualAmount = std::pow(10.0, double(-exponent)) * double(amount);
subtitle += QLocale::system().toCurrencyString(actualAmount, currency);
MessageBuilder builder(systemMessage, parseTagString(subtitle),
calculateMessageTime(message).time());
builder->flags.set(MessageFlag::ElevatedMessage);
return builder.release();
}
void TwitchMessageBuilder::setThread(std::shared_ptr<MessageThread> thread)
{
this->thread_ = std::move(thread);
@@ -86,6 +86,8 @@ public:
QString prefix, const std::vector<HelixModerator> &users,
Channel *channel, MessageBuilder *builder);
static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message);
// Shares some common logic from SharedMessageBuilder::parseBadgeTag
static std::unordered_map<QString, QString> parseBadgeInfoTag(
const QVariantMap &tags);