Respect follower emotes context, making them only available in their owner channels (#2951)

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Paweł 2021-07-11 11:12:49 +02:00 committed by GitHub
parent 2844c8e7e0
commit d5add46730
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 180 additions and 28 deletions

View file

@ -9,6 +9,7 @@
- Bugfix: Fixed large timeout durations in moderation buttons overlapping with usernames or other buttons. (#2865, #2921)
- Bugfix: Middle mouse click no longer scrolls in not fully populated usercards and splits. (#2933)
- Bugfix: Fix bad behavior of the HTML color picker edit when user input is being entered. (#2942)
- Bugfix: Made follower emotes suggested (in emote popup menu, tab completion, emote input menu) only in their origin channel, not globally. (#2951)
- Bugfix: Fixed founder badge not being respected by `author.subbed` filter. (#2971)
- Bugfix: Usercards on IRC will now only show user's messages. (#1780, #2979)

View file

@ -10,6 +10,7 @@
#include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "util/QStringHash.hpp"
#include <QtAlgorithms>
#include <utility>
@ -96,14 +97,24 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
{
// account emotes
if (auto account = getApp()->accounts->twitch.getCurrent())
{
for (const auto &emote : account->accessEmotes()->allEmoteNames)
// Twitch Emotes available globally
for (const auto &emote : account->accessEmotes()->emotes)
{
// XXX: No way to discern between a twitch global emote and sub
// emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
addString(emote.first.string, TaggedString::TwitchGlobalEmote);
}
// Twitch Emotes available locally
auto localEmoteData = account->accessLocalEmotes();
if (localEmoteData->find(channel->roomId()) !=
localEmoteData->end())
{
for (const auto &emote : localEmoteData->at(channel->roomId()))
{
addString(emote.first.string,
TaggedString::Type::TwitchLocalEmote);
}
}
}

View file

@ -23,6 +23,7 @@ class CompletionModel : public QAbstractListModel
BTTVGlobalEmote,
BTTVChannelEmote,
TwitchGlobalEmote,
TwitchLocalEmote,
TwitchSubscriberEmote,
Emoji,
EmoteEnd,

View file

@ -40,7 +40,7 @@ void IvrApi::getBulkEmoteSets(QString emoteSetList,
QUrlQuery urlQuery;
urlQuery.addQueryItem("set_id", emoteSetList);
this->makeRequest("twitch/emoteset", urlQuery)
this->makeRequest("v2/twitch/emotes/sets", urlQuery)
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
auto root = result.parseJsonArray();

View file

@ -2,6 +2,7 @@
#include "common/NetworkRequest.hpp"
#include "messages/Link.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
#include <boost/noncopyable.hpp>
@ -35,7 +36,7 @@ struct IvrEmoteSet {
const QString setId;
const QString displayName;
const QString login;
const QString id;
const QString channelId;
const QString tier;
const QJsonArray emotes;
@ -43,9 +44,9 @@ struct IvrEmoteSet {
: setId(root.value("setID").toString())
, displayName(root.value("channelName").toString())
, login(root.value("channelLogin").toString())
, id(root.value("channelID").toString())
, channelId(root.value("channelID").toString())
, tier(root.value("tier").toString())
, emotes(root.value("emotes").toArray())
, emotes(root.value("emoteList").toArray())
{
}
@ -56,12 +57,18 @@ struct IvrEmote {
const QString id;
const QString setId;
const QString url;
const QString emoteType;
const QString imageType;
IvrEmote(QJsonObject root)
: code(root.value("token").toString())
explicit IvrEmote(QJsonObject root)
: code(root.value("code").toString())
, id(root.value("id").toString())
, setId(root.value("setID").toString())
, url(root.value("url_3x").toString())
, url(QString(TWITCH_EMOTE_TEMPLATE)
.replace("{id}", this->id)
.replace("{scale}", "3.0"))
, emoteType(root.value("type").toString())
, imageType(root.value("assetType").toString())
{
}
};

View file

@ -18,6 +18,7 @@
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/api/Kraken.hpp"
#include "singletons/Emotes.hpp"
#include "util/QStringHash.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
@ -216,7 +217,7 @@ void TwitchAccount::loadEmotes()
// Clearing emote data
auto emoteData = this->emotes_.access();
emoteData->emoteSets.clear();
emoteData->allEmoteNames.clear();
emoteData->emotes.clear();
for (auto emoteSetIt = data.emoteSets.begin();
emoteSetIt != data.emoteSets.end(); ++emoteSetIt)
@ -245,11 +246,14 @@ void TwitchAccount::loadEmotes()
EmoteName{TwitchEmotes::cleanUpEmoteCode(code)};
emoteSet->emotes.emplace_back(
TwitchEmote{id, cleanCode});
emoteData->allEmoteNames.push_back(cleanCode);
auto emote =
getApp()->emotes->twitch.getOrCreateEmote(id, code);
emoteData->emotes.emplace(code, emote);
if (!emoteSet->local)
{
auto emote =
getApp()->emotes->twitch.getOrCreateEmote(id,
code);
emoteData->emotes.emplace(code, emote);
}
}
std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(),
@ -345,6 +349,7 @@ void TwitchAccount::loadUserstateEmotes()
batch.join(","),
[this](QJsonArray emoteSetArray) {
auto emoteData = this->emotes_.access();
auto localEmoteData = this->localEmotes_.access();
for (auto emoteSet : emoteSetArray)
{
auto newUserEmoteSet = std::make_shared<EmoteSet>();
@ -360,9 +365,9 @@ void TwitchAccount::loadUserstateEmotes()
newUserEmoteSet->text = name;
newUserEmoteSet->channelName = ivrEmoteSet.login;
for (const auto &emote : ivrEmoteSet.emotes)
for (const auto &emoteObj : ivrEmoteSet.emotes)
{
IvrEmote ivrEmote(emote.toObject());
IvrEmote ivrEmote(emoteObj.toObject());
auto id = EmoteId{ivrEmote.id};
auto code = EmoteName{ivrEmote.code};
@ -371,11 +376,29 @@ void TwitchAccount::loadUserstateEmotes()
newUserEmoteSet->emotes.push_back(
TwitchEmote{id, cleanCode});
emoteData->allEmoteNames.push_back(cleanCode);
auto twitchEmote =
auto emote =
getApp()->emotes->twitch.getOrCreateEmote(id, code);
emoteData->emotes.emplace(code, twitchEmote);
// Follower emotes can be only used in their origin channel
if (ivrEmote.emoteType == "FOLLOWER")
{
newUserEmoteSet->local = true;
// EmoteMap for target channel wasn't initialized yet, doing it now
if (localEmoteData->find(ivrEmoteSet.channelId) ==
localEmoteData->end())
{
localEmoteData->emplace(ivrEmoteSet.channelId,
EmoteMap());
}
localEmoteData->at(ivrEmoteSet.channelId)
.emplace(code, emote);
}
else
{
emoteData->emotes.emplace(code, emote);
}
}
std::sort(newUserEmoteSet->emotes.begin(),
newUserEmoteSet->emotes.end(),
@ -397,6 +420,12 @@ SharedAccessGuard<const TwitchAccount::TwitchAccountEmoteData>
return this->emotes_.accessConst();
}
SharedAccessGuard<const std::unordered_map<QString, EmoteMap>>
TwitchAccount::accessLocalEmotes() const
{
return this->localEmotes_.accessConst();
}
// AutoModActions
void TwitchAccount::autoModAllow(const QString msgID, ChannelPtr channel)
{
@ -510,6 +539,12 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
getHelix()->getEmoteSetData(
emoteSet->key,
[emoteSet](HelixEmoteSetData emoteSetData) {
// Follower emotes can be only used in their origin channel
if (emoteSetData.emoteType == "follower")
{
emoteSet->local = true;
}
if (emoteSetData.ownerId.isEmpty() ||
emoteSetData.setId != emoteSet->key)
{

View file

@ -7,6 +7,7 @@
#include "controllers/accounts/Account.hpp"
#include "messages/Emote.hpp"
#include "providers/twitch/TwitchUser.hpp"
#include "util/QStringHash.hpp"
#include <rapidjson/document.h>
#include <QColor>
@ -62,6 +63,7 @@ public:
QString key;
QString channelName;
QString text;
bool local{false};
std::vector<TwitchEmote> emotes;
};
@ -70,8 +72,8 @@ public:
struct TwitchAccountEmoteData {
std::vector<std::shared_ptr<EmoteSet>> emoteSets;
std::vector<EmoteName> allEmoteNames;
// this EmoteMap should contain all emotes available globally
// excluding locally available emotes, such as follower ones
EmoteMap emotes;
};
@ -118,6 +120,8 @@ public:
// Returns true if the newly inserted emote sets differ from the ones previously saved
[[nodiscard]] bool setUserstateEmoteSets(QStringList newEmoteSets);
SharedAccessGuard<const TwitchAccountEmoteData> accessEmotes() const;
SharedAccessGuard<const std::unordered_map<QString, EmoteMap>>
accessLocalEmotes() const;
// Automod actions
void autoModAllow(const QString msgID, ChannelPtr channel);
@ -140,6 +144,7 @@ private:
// std::map<UserId, TwitchAccountEmoteData> emotes;
UniqueAccess<TwitchAccountEmoteData> emotes_;
UniqueAccess<std::unordered_map<QString, EmoteMap>> localEmotes_;
};
} // namespace chatterino

View file

@ -4,6 +4,7 @@
#include "common/Common.hpp"
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "messages/Message.hpp"
@ -21,6 +22,7 @@
#include "singletons/WindowManager.hpp"
#include "util/FormatTime.hpp"
#include "util/PostToThread.hpp"
#include "util/QStringHash.hpp"
#include "widgets/Window.hpp"
#include <rapidjson/document.h>
@ -30,7 +32,6 @@
#include <QJsonValue>
#include <QThread>
#include <QTimer>
#include "common/QLogging.hpp"
namespace chatterino {
namespace {

View file

@ -10,6 +10,7 @@
#include "providers/twitch/ChannelPointReward.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "util/QStringHash.hpp"
#include <QColor>
#include <QElapsedTimer>

View file

@ -793,6 +793,43 @@ void Helix::getEmoteSetData(QString emoteSetId,
.execute();
}
void Helix::getChannelEmotes(
QString broadcasterId,
ResultCallback<std::vector<HelixChannelEmote>> successCallback,
HelixFailureCallback failureCallback)
{
QUrlQuery urlQuery;
urlQuery.addQueryItem("broadcaster_id", broadcasterId);
this->makeRequest("chat/emotes", urlQuery)
.onSuccess([successCallback,
failureCallback](NetworkResult result) -> Outcome {
QJsonObject root = result.parseJson();
auto data = root.value("data");
if (!data.isArray())
{
failureCallback();
return Failure;
}
std::vector<HelixChannelEmote> channelEmotes;
for (const auto &jsonStream : data.toArray())
{
channelEmotes.emplace_back(jsonStream.toObject());
}
successCallback(channelEmotes);
return Success;
})
.onError([failureCallback](auto result) {
// TODO: make better xd
failureCallback();
})
.execute();
}
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
{
assert(!url.startsWith("/"));

View file

@ -2,6 +2,7 @@
#include "common/Aliases.hpp"
#include "common/NetworkRequest.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
#include <QJsonArray>
#include <QString>
@ -267,10 +268,31 @@ struct HelixCheermoteSet {
struct HelixEmoteSetData {
QString setId;
QString ownerId;
QString emoteType;
explicit HelixEmoteSetData(QJsonObject jsonObject)
: setId(jsonObject.value("emote_set_id").toString())
, ownerId(jsonObject.value("owner_id").toString())
, emoteType(jsonObject.value("emote_type").toString())
{
}
};
struct HelixChannelEmote {
const QString emoteId;
const QString name;
const QString type;
const QString setId;
const QString url;
explicit HelixChannelEmote(QJsonObject jsonObject)
: emoteId(jsonObject.value("id").toString())
, name(jsonObject.value("name").toString())
, type(jsonObject.value("emote_type").toString())
, setId(jsonObject.value("emote_set_id").toString())
, url(QString(TWITCH_EMOTE_TEMPLATE)
.replace("{id}", this->emoteId)
.replace("{scale}", "3.0"))
{
}
};
@ -414,6 +436,12 @@ public:
ResultCallback<HelixEmoteSetData> successCallback,
HelixFailureCallback failureCallback);
// https://dev.twitch.tv/docs/api/reference#get-channel-emotes
void getChannelEmotes(
QString broadcasterId,
ResultCallback<std::vector<HelixChannelEmote>> successCallback,
HelixFailureCallback failureCallback);
void update(QString clientId, QString oauthToken);
static void initialize();

View file

@ -165,6 +165,13 @@ URL: https://dev.twitch.tv/docs/api/reference#get-emote-sets
Used in:
- `providers/twitch/TwitchAccount.cpp` to set emoteset owner data upon loading subscriber emotes from Kraken
### Get Channel Emotes
URL: https://dev.twitch.tv/docs/api/reference#get-channel-emotes
- We implement this in `providers/twitch/api/Helix.cpp getChannelEmotes`
Not used anywhere at the moment.
## TMI
The TMI api is undocumented.

View file

@ -69,6 +69,12 @@ namespace {
for (const auto &set : sets)
{
// Some emotes (e.g. follower ones) are only available in their origin channel
if (set->local && currentChannelName != set->channelName)
{
continue;
}
// TITLE
auto channelName = set->channelName;
auto text = set->text.isEmpty() ? "Twitch" : set->text;

View file

@ -80,8 +80,20 @@ void InputCompletionPopup::updateEmotes(const QString &text, ChannelPtr channel)
{
if (auto user = getApp()->accounts->twitch.getCurrent())
{
auto twitch = user->accessEmotes();
addEmotes(emotes, twitch->emotes, text, "Twitch Emote");
// Twitch Emotes available globally
auto emoteData = user->accessEmotes();
addEmotes(emotes, emoteData->emotes, text, "Twitch Emote");
// Twitch Emotes available locally
auto localEmoteData = user->accessLocalEmotes();
if (localEmoteData->find(tc->roomId()) != localEmoteData->end())
{
if (auto localEmotes = &localEmoteData->at(tc->roomId()))
{
addEmotes(emotes, *localEmotes, text,
"Local Twitch Emotes");
}
}
}
if (tc)