feat: notate power-up automatic reward redemptions (#5471)
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
- Minor: Moderators can now see when users are warned. (#5441)
|
- Minor: Moderators can now see when users are warned. (#5441)
|
||||||
- Minor: Added support for Brave & google-chrome-stable browsers. (#5452)
|
- Minor: Added support for Brave & google-chrome-stable browsers. (#5452)
|
||||||
- Minor: Added drop indicator line while dragging in tables. (#5256)
|
- 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: 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)
|
- Minor: Introduce HTTP API for plugins. (#5383)
|
||||||
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
|
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
|
||||||
|
|||||||
@@ -14,6 +14,47 @@ ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
|
|||||||
this->title = reward.value("title").toString();
|
this->title = reward.value("title").toString();
|
||||||
this->cost = reward.value("cost").toInt();
|
this->cost = reward.value("cost").toInt();
|
||||||
this->isUserInputRequired = reward.value("is_user_input_required").toBool();
|
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
|
// 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
|
// because we will get the user info from a corresponding IRC message
|
||||||
@@ -27,6 +68,13 @@ ChannelPointReward::ChannelPointReward(const QJsonObject &redemption)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto imageValue = reward.value("image");
|
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
|
// From Twitch docs
|
||||||
// The size is only an estimation, the actual size might vary.
|
// The size is only an estimation, the actual size might vary.
|
||||||
constexpr QSize baseSize(28, 28);
|
constexpr QSize baseSize(28, 28);
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ struct ChannelPointReward {
|
|||||||
int cost;
|
int cost;
|
||||||
ImageSet image;
|
ImageSet image;
|
||||||
bool isUserInputRequired = false;
|
bool isUserInputRequired = false;
|
||||||
|
bool isBits = false;
|
||||||
|
QString emoteId; // currently only for celebrations
|
||||||
|
QString emoteName; // currently only for celebrations
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
QString id;
|
QString id;
|
||||||
|
|||||||
@@ -1338,21 +1338,30 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
|
|||||||
auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
|
auto *channel = dynamic_cast<TwitchChannel *>(chan.get());
|
||||||
|
|
||||||
const auto &tags = message->tags();
|
const auto &tags = message->tags();
|
||||||
|
QString rewardId;
|
||||||
if (const auto it = tags.find("custom-reward-id"); it != tags.end())
|
if (const auto it = tags.find("custom-reward-id"); it != tags.end())
|
||||||
{
|
{
|
||||||
const auto rewardId = it.value().toString();
|
rewardId = it.value().toString();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
|
{
|
||||||
|
rewardId = msgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
QString content = originalContent;
|
||||||
int messageOffset = stripLeadingReplyMention(tags, content);
|
int messageOffset = stripLeadingReplyMention(tags, content);
|
||||||
|
|||||||
@@ -1181,6 +1181,8 @@ void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
|||||||
|
|
||||||
switch (innerMessage.type)
|
switch (innerMessage.type)
|
||||||
{
|
{
|
||||||
|
case PubSubCommunityPointsChannelV1Message::Type::
|
||||||
|
AutomaticRewardRedeemed:
|
||||||
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
|
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
|
||||||
auto redemption =
|
auto redemption =
|
||||||
innerMessage.data.value("redemption").toObject();
|
innerMessage.data.value("redemption").toObject();
|
||||||
|
|||||||
@@ -1594,6 +1594,15 @@ void TwitchMessageBuilder::appendChannelPointRewardMessage(
|
|||||||
}
|
}
|
||||||
builder->emplace<TextElement>(redeemed,
|
builder->emplace<TextElement>(redeemed,
|
||||||
MessageElementFlag::ChannelPointReward);
|
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>(
|
builder->emplace<TextElement>(
|
||||||
reward.title, MessageElementFlag::ChannelPointReward,
|
reward.title, MessageElementFlag::ChannelPointReward,
|
||||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||||
@@ -1602,6 +1611,12 @@ void TwitchMessageBuilder::appendChannelPointRewardMessage(
|
|||||||
builder->emplace<TextElement>(
|
builder->emplace<TextElement>(
|
||||||
QString::number(reward.cost), MessageElementFlag::ChannelPointReward,
|
QString::number(reward.cost), MessageElementFlag::ChannelPointReward,
|
||||||
MessageColor::Text, FontStyle::ChatMediumBold);
|
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||||
|
if (reward.isBits)
|
||||||
|
{
|
||||||
|
builder->emplace<TextElement>(
|
||||||
|
"bits", MessageElementFlag::ChannelPointReward, MessageColor::Text,
|
||||||
|
FontStyle::ChatMediumBold);
|
||||||
|
}
|
||||||
if (reward.isUserInputRequired)
|
if (reward.isUserInputRequired)
|
||||||
{
|
{
|
||||||
builder->emplace<LinebreakElement>(
|
builder->emplace<LinebreakElement>(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace chatterino {
|
|||||||
|
|
||||||
struct PubSubCommunityPointsChannelV1Message {
|
struct PubSubCommunityPointsChannelV1Message {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
AutomaticRewardRedeemed,
|
||||||
RewardRedeemed,
|
RewardRedeemed,
|
||||||
|
|
||||||
INVALID,
|
INVALID,
|
||||||
@@ -30,6 +31,9 @@ constexpr magic_enum::customize::customize_t magic_enum::customize::enum_name<
|
|||||||
{
|
{
|
||||||
switch (value)
|
switch (value)
|
||||||
{
|
{
|
||||||
|
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
|
||||||
|
AutomaticRewardRedeemed:
|
||||||
|
return "automatic-reward-redeemed";
|
||||||
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
|
case chatterino::PubSubCommunityPointsChannelV1Message::Type::
|
||||||
RewardRedeemed:
|
RewardRedeemed:
|
||||||
return "reward-redeemed";
|
return "reward-redeemed";
|
||||||
|
|||||||
Reference in New Issue
Block a user