feat: Add Live Emote Updates for BTTV (#4147)

This feature is enabled by default and can be disabled in settings with the "Enable BTTV live emotes updates" setting.

Co-authored-by: Felanbird <41973452+Felanbird@users.noreply.github.com>
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
nerix
2023-01-21 15:06:55 +01:00
committed by GitHub
parent 56f7c91a64
commit 904749cf62
22 changed files with 856 additions and 33 deletions
+147 -26
View File
@@ -7,6 +7,7 @@
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/liveupdates/BttvLiveUpdateMessages.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "singletons/Settings.hpp"
@@ -21,6 +22,12 @@ namespace {
QString emoteLinkFormat("https://betterttv.com/emotes/%1");
struct CreateEmoteResult {
EmoteId id;
EmoteName name;
Emote emote;
};
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
@@ -70,6 +77,70 @@ namespace {
return {Success, std::move(emotes)};
}
CreateEmoteResult createChannelEmote(const QString &channelDisplayName,
const QJsonObject &jsonEmote)
{
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
auto author = EmoteAuthor{
jsonEmote.value("user").toObject().value("displayName").toString()};
auto emote = Emote({
name,
ImageSet{
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
},
Tooltip{
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
.arg(name.string)
// when author is empty, it is a channel emote created by the broadcaster
.arg(author.string.isEmpty() ? "Channel" : "Shared")
.arg(author.string.isEmpty() ? channelDisplayName
: author.string)},
Url{emoteLinkFormat.arg(id.string)},
false,
id,
});
return {id, name, emote};
}
bool updateChannelEmote(Emote &emote, const QString &channelDisplayName,
const QJsonObject &jsonEmote)
{
bool anyModifications = false;
if (jsonEmote.contains("code"))
{
emote.name = EmoteName{jsonEmote.value("code").toString()};
anyModifications = true;
}
if (jsonEmote.contains("user"))
{
emote.author = EmoteAuthor{jsonEmote.value("user")
.toObject()
.value("displayName")
.toString()};
anyModifications = true;
}
if (anyModifications)
{
emote.tooltip = Tooltip{
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
.arg(emote.name.string)
// when author is empty, it is a channel emote created by the broadcaster
.arg(emote.author.string.isEmpty() ? "Channel" : "Shared")
.arg(emote.author.string.isEmpty() ? channelDisplayName
: emote.author.string)};
}
return anyModifications;
}
std::pair<Outcome, EmoteMap> parseChannelEmotes(
const QJsonObject &jsonRoot, const QString &channelDisplayName)
{
@@ -80,33 +151,11 @@ namespace {
auto jsonEmotes = jsonRoot.value(key).toArray();
for (auto jsonEmote_ : jsonEmotes)
{
auto jsonEmote = jsonEmote_.toObject();
auto emote = createChannelEmote(channelDisplayName,
jsonEmote_.toObject());
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
auto author = EmoteAuthor{jsonEmote.value("user")
.toObject()
.value("displayName")
.toString()};
auto emote = Emote({
name,
ImageSet{
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1),
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5),
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25),
},
Tooltip{
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
.arg(name.string)
// when author is empty, it is a channel emote created by the broadcaster
.arg(author.string.isEmpty() ? "Channel" : "Shared")
.arg(author.string.isEmpty() ? channelDisplayName
: author.string)},
Url{emoteLinkFormat.arg(id.string)},
});
emotes[name] = cachedOrMake(std::move(emote), id);
emotes[emote.name] =
cachedOrMake(std::move(emote.emote), emote.id);
}
};
@@ -227,6 +276,78 @@ void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
.execute();
}
EmotePtr BttvEmotes::addEmote(
const QString &channelDisplayName,
Atomic<std::shared_ptr<const EmoteMap>> &channelEmoteMap,
const BttvLiveUpdateEmoteUpdateAddMessage &message)
{
// This copies the map.
EmoteMap updatedMap = *channelEmoteMap.get();
auto result = createChannelEmote(channelDisplayName, message.jsonEmote);
auto emote = std::make_shared<const Emote>(std::move(result.emote));
updatedMap[result.name] = emote;
channelEmoteMap.set(std::make_shared<EmoteMap>(std::move(updatedMap)));
return emote;
}
boost::optional<std::pair<EmotePtr, EmotePtr>> BttvEmotes::updateEmote(
const QString &channelDisplayName,
Atomic<std::shared_ptr<const EmoteMap>> &channelEmoteMap,
const BttvLiveUpdateEmoteUpdateAddMessage &message)
{
// This copies the map.
EmoteMap updatedMap = *channelEmoteMap.get();
// Step 1: remove the existing emote
auto it = updatedMap.findEmote(QString(), message.emoteID);
if (it == updatedMap.end())
{
// We already copied the map at this point and are now discarding the copy.
// This is fine, because this case should be really rare.
return boost::none;
}
auto oldEmotePtr = it->second;
// copy the existing emote, to not change the original one
auto emote = *oldEmotePtr;
updatedMap.erase(it);
// Step 2: update the emote
if (!updateChannelEmote(emote, channelDisplayName, message.jsonEmote))
{
// The emote wasn't actually updated
return boost::none;
}
auto name = emote.name;
auto emotePtr = std::make_shared<const Emote>(std::move(emote));
updatedMap[name] = emotePtr;
channelEmoteMap.set(std::make_shared<EmoteMap>(std::move(updatedMap)));
return std::make_pair(oldEmotePtr, emotePtr);
}
boost::optional<EmotePtr> BttvEmotes::removeEmote(
Atomic<std::shared_ptr<const EmoteMap>> &channelEmoteMap,
const BttvLiveUpdateEmoteRemoveMessage &message)
{
// This copies the map.
EmoteMap updatedMap = *channelEmoteMap.get();
auto it = updatedMap.findEmote(QString(), message.emoteID);
if (it == updatedMap.end())
{
// We already copied the map at this point and are now discarding the copy.
// This is fine, because this case should be really rare.
return boost::none;
}
auto emote = it->second;
updatedMap.erase(it);
channelEmoteMap.set(std::make_shared<EmoteMap>(std::move(updatedMap)));
return emote;
}
/*
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)