mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
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:
parent
2844c8e7e0
commit
d5add46730
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class CompletionModel : public QAbstractListModel
|
|||
BTTVGlobalEmote,
|
||||
BTTVChannelEmote,
|
||||
TwitchGlobalEmote,
|
||||
TwitchLocalEmote,
|
||||
TwitchSubscriberEmote,
|
||||
Emoji,
|
||||
EmoteEnd,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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("/"));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue