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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user