Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subtitle to Hype Chats #4715

Merged
merged 6 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 96 additions & 7 deletions src/providers/twitch/IrcMessageHandler.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -26,12 +27,17 @@
#include "util/StreamerMode.hpp"

#include <IrcMessage>
#include <QLocale>
#include <QStringBuilder>

#include <chrono>
#include <memory>
#include <unordered_set>

namespace {
using namespace chatterino;
using namespace chatterino::literals;
using namespace std::chrono_literals;

// Message types below are the ones that might contain special user's message on USERNOTICE
static const QSet<QString> specialMessageTypes{
Expand All @@ -43,6 +49,19 @@ static const QSet<QString> specialMessageTypes{
"announcement", // new mod announcement thing
};

struct PinnedChatPaidLevel {
std::chrono::duration<uint32_t> duration;
uint8_t numeric;
};

const QHash<QString, PinnedChatPaidLevel> PINNED_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}},
};

MessagePtr generateBannedMessage(bool confirmedBan)
{
const auto linkColor = MessageColor(MessageColor::Link);
Expand Down Expand Up @@ -156,9 +175,60 @@ 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);
}

MessagePtr parsePinnedChat(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 = PINNED_CHAT_PAID_LEVEL.find(level);
if (levelIt != PINNED_CHAT_PAID_LEVEL.end())
{
subtitle = u"Level %1 Hype Chat ("_s.arg(levelIt->numeric) %
formatTime(int(levelIt->duration.count())) % (") ");
}
else
{
subtitle = u"Hype Chat "_s;
}

// actualAmount = amount * 10^(-exponent)
double actualAmount = std::pow(10.0, double(-exponent)) * double(amount);
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
subtitle += QLocale::system().toCurrencyString(actualAmount, currency);

MessageBuilder builder(systemMessage, parseTagString(subtitle),
calculateMessageTime(message).time());
builder->flags.set(MessageFlag::ElevatedMessage);
return builder.release();
}

} // namespace
namespace chatterino {

using namespace literals;

static float relativeSimilarity(const QString &str1, const QString &str2)
{
// Longest Common Substring Problem
Expand Down Expand Up @@ -314,6 +384,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 = parsePinnedChat(message);
if (ptr)
{
builtMessages.emplace_back(std::move(ptr));
}
}

return builtMessages;
}

Expand All @@ -330,6 +410,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 = parsePinnedChat(message);
if (ptr)
{
chan->addMessage(ptr);
}
}
}

std::vector<MessagePtr> IrcMessageHandler::parseMessageWithReply(
Expand Down Expand Up @@ -442,13 +537,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())
{
Expand Down
6 changes: 6 additions & 0 deletions src/util/SampleData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ const QStringList &getSampleMiscMessages()
R"(@badge-info=subscriber/47;badges=broadcaster/1,subscriber/3012,twitchconAmsterdam2020/1;color=#FF0000;display-name=Supinic;emotes=;flags=;id=8c26e1ab-b50c-4d9d-bc11-3fd57a941d90;login=supinic;mod=0;msg-id=announcement;msg-param-color=PRIMARY;room-id=31400525;subscriber=1;system-msg=;tmi-sent-ts=1648762219962;user-id=31400525;user-type= :tmi.twitch.tv USERNOTICE #supinic :mm test lol)",

// Elevated Message (Paid option for keeping a message in chat longer)
// no level
R"(@badge-info=subscriber/3;badges=subscriber/0,bits-charity/1;color=#0000FF;display-name=SnoopyTheBot;emotes=;first-msg=0;flags=;id=8779a9e5-cf1b-47b3-b9fe-67a5b1b605f6;mod=0;pinned-chat-paid-amount=500;pinned-chat-paid-canonical-amount=5;pinned-chat-paid-currency=USD;pinned-chat-paid-exponent=2;returning-chatter=0;room-id=36340781;subscriber=1;tmi-sent-ts=1664505974154;turbo=0;user-id=136881249;user-type= :snoopythebot!snoopythebot@snoopythebot.tmi.twitch.tv PRIVMSG #pajlada :-$5)",
// level 1
R"(@pinned-chat-paid-level=ONE;mod=0;flags=;pinned-chat-paid-amount=1400;pinned-chat-paid-exponent=2;tmi-sent-ts=1687970631828;subscriber=1;user-type=;color=#9DA364;emotes=;badges=predictions/blue-1,subscriber/60,twitchconAmsterdam2020/1;pinned-chat-paid-canonical-amount=1400;turbo=0;user-id=26753388;id=e6681ba0-cdc6-4482-93a3-515b74361e8b;room-id=36340781;first-msg=0;returning-chatter=0;pinned-chat-paid-currency=NOK;pinned-chat-paid-is-system-message=0;badge-info=predictions/Day\s53/53\sforsenSmug,subscriber/67;display-name=matrHS :matrhs!matrhs@matrhs.tmi.twitch.tv PRIVMSG #pajlada :Title: Beating the record. but who is recordingLOL)",
R"(@flags=;pinned-chat-paid-amount=8761;turbo=0;user-id=35669184;pinned-chat-paid-level=ONE;user-type=;pinned-chat-paid-canonical-amount=8761;badge-info=subscriber/2;badges=subscriber/2,sub-gifter/1;emotes=;pinned-chat-paid-exponent=2;subscriber=1;mod=0;room-id=36340781;returning-chatter=0;id=289b614d-1837-4cff-ac22-ce33a9735323;first-msg=0;tmi-sent-ts=1687631719188;color=#00FF7F;pinned-chat-paid-currency=RUB;display-name=Danis;pinned-chat-paid-is-system-message=0 :danis!danis@danis.tmi.twitch.tv PRIVMSG #pajlada :-1 lulw)",
// level 2
R"(@room-id=36340781;tmi-sent-ts=1687970634371;flags=;id=39a80a3d-c16e-420f-9bbb-faba4976a3bb;badges=subscriber/6,premium/1;emotes=;display-name=rickharrisoncoc;pinned-chat-paid-level=TWO;turbo=0;pinned-chat-paid-amount=500;pinned-chat-paid-is-system-message=0;color=#FF69B4;subscriber=1;user-type=;first-msg=0;pinned-chat-paid-currency=USD;pinned-chat-paid-canonical-amount=500;user-id=518404689;badge-info=subscriber/10;pinned-chat-paid-exponent=2;returning-chatter=0;mod=0 :rickharrisoncoc!rickharrisoncoc@rickharrisoncoc.tmi.twitch.tv PRIVMSG #pajlada :forsen please read my super chat. Please.)",
};
return list;
}
Expand Down