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

feat: support 7TV overrides and emoji-emotes #283

Draft
wants to merge 2 commits into
base: chatterino7
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/messages/Emote.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct Emote {
Tooltip tooltip;
Url homePage;
bool zeroWidth{};
bool isGlobalOverride = false;
EmoteId id;
EmoteAuthor author;
/**
Expand Down
72 changes: 43 additions & 29 deletions src/messages/MessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2133,24 +2133,6 @@ void MessageBuilder::addEmoji(const EmotePtr &emote)

void MessageBuilder::addTextOrEmote(TextState &state, QString string)
{
if (state.hasBits && this->tryAppendCheermote(state, string))
{
// This string was parsed as a cheermote
return;
}

// TODO: Implement ignored emotes
// Format of ignored emotes:
// Emote name: "forsenPuke" - if string in ignoredEmotes
// Will match emote regardless of source (i.e. bttv, ffz)
// Emote source + name: "bttv:nyanPls"
if (this->tryAppendEmote(state.twitchChannel, state.userID, {string}))
{
// Successfully appended an emote
return;
}

// Actually just text
auto link = linkparser::parse(string);
auto textColor = this->textColor_;

Expand Down Expand Up @@ -2639,6 +2621,13 @@ Outcome MessageBuilder::tryAppendEmote(TwitchChannel *twitchChannel,
return Failure;
}

this->appendEmote(*emote, flags, zeroWidth);
return Success;
}

void MessageBuilder::appendEmote(const EmotePtr &emote,
MessageElementFlags flags, bool zeroWidth)
{
if (zeroWidth && getSettings()->enableZeroWidthEmotes && !this->isEmpty())
{
// Attempt to merge current zero-width emote into any previous emotes
Expand All @@ -2651,26 +2640,25 @@ Outcome MessageBuilder::tryAppendEmote(TwitchChannel *twitchChannel,
auto baseEmoteElement = this->releaseBack();

std::vector<LayeredEmoteElement::Emote> layers = {
{baseEmote, baseEmoteElement->getFlags()}, {*emote, flags}};
{baseEmote, baseEmoteElement->getFlags()}, {emote, flags}};
this->emplace<LayeredEmoteElement>(
std::move(layers), baseEmoteElement->getFlags() | flags,
this->textColor_);
return Success;
return;
}

auto *asLayered = dynamic_cast<LayeredEmoteElement *>(&this->back());
if (asLayered)
{
asLayered->addEmoteLayer({*emote, flags});
asLayered->addEmoteLayer({emote, flags});
asLayered->addFlags(flags);
return Success;
return;
}

// No emote to merge with, just show as regular emote
}

this->emplace<EmoteElement>(*emote, flags, this->textColor_);
return Success;
this->emplace<EmoteElement>(emote, flags, this->textColor_);
}

void MessageBuilder::addWords(
Expand All @@ -2696,10 +2684,18 @@ void MessageBuilder::addWords(

if (currentTwitchEmote.start == cursor)
{
// This emote exists right at the start of the word!
this->emplace<EmoteElement>(currentTwitchEmote.ptr,
MessageElementFlag::TwitchEmote,
this->textColor_);
auto [emote, flags, zeroWidth] = parseEmote(
state.twitchChannel, state.userID, currentTwitchEmote.name);
if (emote && emote->get()->isGlobalOverride)
{
this->appendEmote(*emote, flags, zeroWidth);
}
else
{
this->emplace<EmoteElement>(currentTwitchEmote.ptr,
MessageElementFlag::TwitchEmote,
this->textColor_);
}

auto len = currentTwitchEmote.name.string.length();
cursor += len;
Expand Down Expand Up @@ -2750,7 +2746,25 @@ void MessageBuilder::addWords(
continue;
}

// split words
if (state.hasBits && this->tryAppendCheermote(state, word))
{
// This string was parsed as a cheermote
cursor += word.size() + 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: narrowing conversion from 'qsizetype' (aka 'long long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

rmote
                            ^

continue;
}

// TODO: Implement ignored emotes
// Format of ignored emotes:
// Emote name: "forsenPuke" - if string in ignoredEmotes
// Will match emote regardless of source (i.e. bttv, ffz)
// Emote source + name: "bttv:nyanPls"
if (this->tryAppendEmote(state.twitchChannel, state.userID, {word}))
{
// Successfully appended an emote
cursor += word.size() + 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: narrowing conversion from 'qsizetype' (aka 'long long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]

emote
                            ^

continue;
}

for (auto variant : getApp()->getEmotes()->getEmojis()->parse(word))
{
boost::apply_visitor(variant::Overloaded{
Expand Down
2 changes: 2 additions & 0 deletions src/messages/MessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ class MessageBuilder
Outcome tryAppendCheermote(TextState &state, const QString &string);
Outcome tryAppendEmote(TwitchChannel *twitchChannel, const QString &userID,
const EmoteName &name);
void appendEmote(const EmotePtr &emote, MessageElementFlags flags,
bool zeroWidth);

bool isEmpty() const;
MessageElement &back();
Expand Down
1 change: 1 addition & 0 deletions src/providers/bttv/BttvEmotes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ CreateEmoteResult createChannelEmote(const QString &channelDisplayName,
: author.string)},
Url{EMOTE_LINK_FORMAT.arg(id.string)},
false,
false,
id,
});

Expand Down
60 changes: 35 additions & 25 deletions src/providers/seventv/SeventvEmotes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,12 @@ EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
}

/**
* This decides whether an emote should be displayed
* as zero-width
* Gets the active emote flags
*/
bool isZeroWidthActive(const QJsonObject &activeEmote)
SeventvActiveEmoteFlags getActiveFlags(const QJsonObject &activeEmote)
{
auto flags = SeventvActiveEmoteFlags(
SeventvActiveEmoteFlag(activeEmote.value("flags").toInt()));
return flags.has(SeventvActiveEmoteFlag::ZeroWidth);
return static_cast<SeventvActiveEmoteFlag>(
activeEmote.value("flags").toInt());
}

/**
Expand Down Expand Up @@ -135,7 +133,11 @@ CreateEmoteResult createEmote(const QJsonObject &activeEmote,
auto author =
EmoteAuthor{emoteData["owner"].toObject()["display_name"].toString()};
auto baseEmoteName = EmoteName{emoteData["name"].toString()};
bool zeroWidth = isZeroWidthActive(activeEmote);
auto flags = getActiveFlags(activeEmote);
bool zeroWidth = flags.has(SeventvActiveEmoteFlag::ZeroWidth);
bool overrideGlobal =
flags.hasAll(SeventvActiveEmoteFlag::OverrideTwitchGlobal,
SeventvActiveEmoteFlag::OverrideTwitchSubscriber);
bool aliasedName = emoteName != baseEmoteName;
auto tooltip =
aliasedName
Expand All @@ -144,16 +146,17 @@ CreateEmoteResult createEmote(const QJsonObject &activeEmote,
: createTooltip(emoteName.string, author.string, kind);
auto imageSet = SeventvEmotes::createImageSet(emoteData, false);

auto emote = Emote({
emoteName,
imageSet,
tooltip,
Url{EMOTE_LINK_FORMAT.arg(emoteId.string)},
zeroWidth,
emoteId,
author,
makeConditionedOptional(aliasedName, baseEmoteName),
});
Emote emote{
.name = emoteName,
.images = imageSet,
.tooltip = tooltip,
.homePage = Url{EMOTE_LINK_FORMAT.arg(emoteId.string)},
.zeroWidth = zeroWidth,
.isGlobalOverride = overrideGlobal,
.id = emoteId,
.author = author,
.baseName = makeConditionedOptional(aliasedName, baseEmoteName),
};

return {emote, emoteId, emoteName, !emote.images.getImage1()->isEmpty()};
}
Expand Down Expand Up @@ -187,14 +190,21 @@ EmotePtr createUpdatedEmote(const EmotePtr &oldEmote,
dispatch.emoteName == oldEmote->baseName->string;

auto baseName = oldEmote->baseName.value_or(oldEmote->name);
auto emote = std::make_shared<const Emote>(Emote(
{EmoteName{dispatch.emoteName}, oldEmote->images,
toNonAliased
? createTooltip(dispatch.emoteName, oldEmote->author.string, kind)
: createAliasedTooltip(dispatch.emoteName, baseName.string,
oldEmote->author.string, kind),
oldEmote->homePage, oldEmote->zeroWidth, oldEmote->id,
oldEmote->author, makeConditionedOptional(!toNonAliased, baseName)}));
auto emote = std::make_shared<const Emote>(Emote({
.name = EmoteName{dispatch.emoteName},
.images = oldEmote->images,
.tooltip = toNonAliased ? createTooltip(dispatch.emoteName,
oldEmote->author.string, kind)
: createAliasedTooltip(
dispatch.emoteName, baseName.string,
oldEmote->author.string, kind),
.homePage = oldEmote->homePage,
.zeroWidth = oldEmote->zeroWidth,
.isGlobalOverride = oldEmote->isGlobalOverride,
.id = oldEmote->id,
.author = oldEmote->author,
.baseName = makeConditionedOptional(!toNonAliased, baseName),
}));
return emote;
}

Expand Down
36 changes: 8 additions & 28 deletions tests/snapshots/IrcMessageHandler/rm-deleted.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,39 +179,19 @@
]
},
{
"emote": {
"author": "Chatterino",
"homePage": "https://chatterino.com/7TVEmote",
"id": "1",
"images": {
"1x": "https://chatterino.com/7TVEmote.png"
},
"name": "7TVEmote",
"tooltip": "7TVEmote Tooltip"
},
"flags": "SevenTVEmoteImage|SevenTVEmoteText",
"color": "Text",
"flags": "Text",
"link": {
"type": "None",
"value": ""
},
"text": {
"color": "Text",
"flags": "Misc",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"words": [
"7TVEmote"
]
},
"tooltip": "7TVEmote Tooltip",
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "EmoteElement"
"type": "TextElement",
"words": [
"7TVEmote"
]
},
{
"emote": {
Expand Down
Loading