Skip to content

Commit

Permalink
feat: notate power-up automatic reward redemptions (#5471)
Browse files Browse the repository at this point in the history
  • Loading branch information
iProdigy authored Jun 22, 2024
1 parent c01bfcf commit 2ef3306
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Minor: Moderators can now see when users are warned. (#5441)
- Minor: Added support for Brave & google-chrome-stable browsers. (#5452)
- Minor: Added drop indicator line while dragging in tables. (#5256)
- Minor: Add channel points indication for new bits power-up redemptions. (#5471)
- Minor: Added `/warn <username> <reason>` command for mods. This prevents the user from chatting until they acknowledge the warning. (#5474)
- Minor: Introduce HTTP API for plugins. (#5383)
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
Expand Down
48 changes: 48 additions & 0 deletions src/providers/twitch/ChannelPointReward.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
this->title = reward.value("title").toString();
this->cost = reward.value("cost").toInt();
this->isUserInputRequired = reward.value("is_user_input_required").toBool();
this->isBits = reward.value("pricing_type").toString() == "BITS";

// accommodate idiosyncrasies of automatic reward redemptions
const auto rewardType = reward.value("reward_type").toString();
if (rewardType == "SEND_ANIMATED_MESSAGE")
{
this->id = "animated-message";
this->isUserInputRequired = true;
this->title = "Message Effects";
}
else if (rewardType == "SEND_GIGANTIFIED_EMOTE")
{
this->id = "gigantified-emote-message";
this->isUserInputRequired = true;
this->title = "Gigantify an Emote";
}
else if (rewardType == "CELEBRATION")
{
this->id = rewardType;
this->title = "On-Screen Celebration";
const auto metadata =
redemption.value("redemption_metadata").toObject();
const auto emote = metadata.value("celebration_emote_metadata")
.toObject()
.value("emote")
.toObject();
this->emoteId = emote.value("id").toString();
this->emoteName = emote.value("token").toString();
}

// use bits cost when channel points were not used
if (cost == 0)
{
this->cost = reward.value("bits_cost").toInt();
}

// workaround twitch bug where bits_cost is always 0 in practice
if (cost == 0)
{
this->cost = reward.value("default_bits_cost").toInt();
}

// We don't need to store user information for rewards with user input
// because we will get the user info from a corresponding IRC message
Expand All @@ -27,6 +68,13 @@ ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
}

auto imageValue = reward.value("image");

// automatic reward redemptions have specialized default images
if (imageValue.isNull() && this->isBits)
{
imageValue = reward.value("default_image");
}

// From Twitch docs
// The size is only an estimation, the actual size might vary.
constexpr QSize baseSize(28, 28);
Expand Down
3 changes: 3 additions & 0 deletions src/providers/twitch/ChannelPointReward.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct ChannelPointReward {
int cost;
ImageSet image;
bool isUserInputRequired = false;
bool isBits = false;
QString emoteId; // currently only for celebrations
QString emoteName; // currently only for celebrations

struct {
QString id;
Expand Down
29 changes: 19 additions & 10 deletions src/providers/twitch/IrcMessageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1338,21 +1338,30 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
auto *channel = dynamic_cast<TwitchChannel *>(chan.get());

const auto &tags = message->tags();
QString rewardId;
if (const auto it = tags.find("custom-reward-id"); it != tags.end())
{
const auto rewardId = it.value().toString();
if (!rewardId.isEmpty() &&
!channel->isChannelPointRewardKnown(rewardId))
rewardId = it.value().toString();
}
else if (const auto typeIt = tags.find("msg-id"); typeIt != tags.end())
{
// slight hack to treat bits power-ups as channel point redemptions
const auto msgId = typeIt.value().toString();
if (msgId == "animated-message" || msgId == "gigantified-emote-message")
{
// Need to wait for pubsub reward notification
qCDebug(chatterinoTwitch) << "TwitchChannel reward added ADD "
"callback since reward is not known:"
<< rewardId;
channel->addQueuedRedemption(rewardId, originalContent, message);
return;
rewardId = msgId;
}
args.channelPointRewardId = rewardId;
}
if (!rewardId.isEmpty() && !channel->isChannelPointRewardKnown(rewardId))
{
// Need to wait for pubsub reward notification
qCDebug(chatterinoTwitch) << "TwitchChannel reward added ADD "
"callback since reward is not known:"
<< rewardId;
channel->addQueuedRedemption(rewardId, originalContent, message);
return;
}
args.channelPointRewardId = rewardId;

QString content = originalContent;
int messageOffset = stripLeadingReplyMention(tags, content);
Expand Down
2 changes: 2 additions & 0 deletions src/providers/twitch/PubSubManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,8 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)

switch (innerMessage.type)
{
case PubSubCommunityPointsChannelV1Message::Type::
AutomaticRewardRedeemed:
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
auto redemption =
innerMessage.data.value("redemption").toObject();
Expand Down
15 changes: 15 additions & 0 deletions src/providers/twitch/TwitchMessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,15 @@ void TwitchMessageBuilder::appendChannelPointRewardMessage(
}
builder->emplace<TextElement>(redeemed,
MessageElementFlag::ChannelPointReward);
if (reward.id == "CELEBRATION")
{
const auto emotePtr =
getIApp()->getEmotes()->getTwitchEmotes()->getOrCreateEmote(
EmoteId{reward.emoteId}, EmoteName{reward.emoteName});
builder->emplace<EmoteElement>(emotePtr,
MessageElementFlag::ChannelPointReward,
MessageColor::Text);
}
builder->emplace<TextElement>(
reward.title, MessageElementFlag::ChannelPointReward,
MessageColor::Text, FontStyle::ChatMediumBold);
Expand All @@ -1602,6 +1611,12 @@ void TwitchMessageBuilder::appendChannelPointRewardMessage(
builder->emplace<TextElement>(
QString::number(reward.cost), MessageElementFlag::ChannelPointReward,
MessageColor::Text, FontStyle::ChatMediumBold);
if (reward.isBits)
{
builder->emplace<TextElement>(
"bits", MessageElementFlag::ChannelPointReward, MessageColor::Text,
FontStyle::ChatMediumBold);
}
if (reward.isUserInputRequired)
{
builder->emplace<LinebreakElement>(
Expand Down
4 changes: 4 additions & 0 deletions src/providers/twitch/pubsubmessages/ChannelPoints.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace chatterino {

struct PubSubCommunityPointsChannelV1Message {
enum class Type {
AutomaticRewardRedeemed,
RewardRedeemed,

INVALID,
Expand All @@ -30,6 +31,9 @@ constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
{
switch (value)
{
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
AutomaticRewardRedeemed:
return "automatic-reward-redeemed";
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
RewardRedeemed:
return "reward-redeemed";
Expand Down

0 comments on commit 2ef3306

Please sign in to comment.