feat: warn for missing scopes when logging in (#6072)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
- Minor: The font weight of chat messages can now be changed. (#6037)
|
- Minor: The font weight of chat messages can now be changed. (#6037)
|
||||||
- Minor: Messages from restricted users can now be hidden when in streamer mode. This is enabled by default. (#6042, #6049)
|
- Minor: Messages from restricted users can now be hidden when in streamer mode. This is enabled by default. (#6042, #6049)
|
||||||
- Minor: Added a tab style option allowing you to make your tabs more compact. (#5858)
|
- Minor: Added a tab style option allowing you to make your tabs more compact. (#5858)
|
||||||
|
- Minor: Chatterino now warns about missing scopes when logging in. (#6072)
|
||||||
- Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846)
|
- Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846)
|
||||||
- Bugfix: Fixed a crash relating to Lua HTTP. (#5800)
|
- Bugfix: Fixed a crash relating to Lua HTTP. (#5800)
|
||||||
- Bugfix: Fixed a crash that could occur on Linux and macOS when clicking "Install" from the update prompt. (#5818)
|
- Bugfix: Fixed a crash that could occur on Linux and macOS when clicking "Install" from the update prompt. (#5818)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#define QT_NO_CAST_FROM_ASCII // avoids unexpected implicit casts
|
#define QT_NO_CAST_FROM_ASCII // avoids unexpected implicit casts
|
||||||
#include "common/LinkParser.hpp"
|
#include "common/LinkParser.hpp"
|
||||||
|
|
||||||
#include "util/QCompareCaseInsensitive.hpp"
|
#include "util/QCompareTransparent.hpp"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
#include "common/ChatterinoSetting.hpp"
|
||||||
#include "common/SignalVector.hpp"
|
#include "common/SignalVector.hpp"
|
||||||
#include "util/QCompareCaseInsensitive.hpp"
|
#include "util/QCompareTransparent.hpp"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
|||||||
@@ -2057,6 +2057,32 @@ MessagePtr MessageBuilder::makeLowTrustUpdateMessage(
|
|||||||
return builder.release();
|
return builder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessagePtrMut MessageBuilder::makeMissingScopesMessage(
|
||||||
|
const QString &missingScopes)
|
||||||
|
{
|
||||||
|
auto warnText = u"Your account is missing the following permission(s): " %
|
||||||
|
missingScopes %
|
||||||
|
u". Some features might not work correctly.";
|
||||||
|
auto linkText = u"Consider re-adding your account."_s;
|
||||||
|
|
||||||
|
MessageBuilder builder;
|
||||||
|
auto text = warnText % ' ' % linkText;
|
||||||
|
builder->messageText = text;
|
||||||
|
builder->searchText = text;
|
||||||
|
builder->flags.set(MessageFlag::System,
|
||||||
|
MessageFlag::DoNotTriggerNotification);
|
||||||
|
|
||||||
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TextElement>(warnText, MessageElementFlag::Text,
|
||||||
|
MessageColor::System);
|
||||||
|
builder
|
||||||
|
.emplace<TextElement>(linkText, MessageElementFlag::Text,
|
||||||
|
MessageColor::Link)
|
||||||
|
->setLink({Link::OpenAccountsPage, {}});
|
||||||
|
|
||||||
|
return builder.release();
|
||||||
|
}
|
||||||
|
|
||||||
MessagePtrMut MessageBuilder::makeClearChatMessage(const QDateTime &now,
|
MessagePtrMut MessageBuilder::makeClearChatMessage(const QDateTime &now,
|
||||||
const QString &actor,
|
const QString &actor,
|
||||||
uint32_t count)
|
uint32_t count)
|
||||||
|
|||||||
@@ -270,6 +270,8 @@ public:
|
|||||||
const QVariantMap &tags,
|
const QVariantMap &tags,
|
||||||
const QTime &time);
|
const QTime &time);
|
||||||
|
|
||||||
|
static MessagePtrMut makeMissingScopesMessage(const QString &missingScopes);
|
||||||
|
|
||||||
/// "Chat has been cleared by a moderator." or "{actor} cleared the chat."
|
/// "Chat has been cleared by a moderator." or "{actor} cleared the chat."
|
||||||
/// @param actor The user who cleared the chat (empty if unknown)
|
/// @param actor The user who cleared the chat (empty if unknown)
|
||||||
/// @param count How many times this message has been received already
|
/// @param count How many times this message has been received already
|
||||||
|
|||||||
@@ -3,15 +3,198 @@
|
|||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Args.hpp"
|
#include "common/Args.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "common/Literals.hpp"
|
||||||
|
#include "common/network/NetworkResult.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchUser.hpp"
|
#include "providers/twitch/TwitchUser.hpp"
|
||||||
|
#include "util/QCompareTransparent.hpp"
|
||||||
#include "util/SharedPtrElementLess.hpp"
|
#include "util/SharedPtrElementLess.hpp"
|
||||||
|
|
||||||
|
#include <QStringBuilder>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
using namespace literals;
|
||||||
|
|
||||||
|
QString missingScopes(const QJsonArray &scopesArray)
|
||||||
|
{
|
||||||
|
std::set<QString, QCompareTransparent> scopes;
|
||||||
|
for (auto s : scopesArray)
|
||||||
|
{
|
||||||
|
scopes.emplace(s.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString missingList;
|
||||||
|
for (auto scope : AUTH_SCOPES)
|
||||||
|
{
|
||||||
|
if (!scopes.contains(scope))
|
||||||
|
{
|
||||||
|
if (!missingList.isEmpty())
|
||||||
|
{
|
||||||
|
missingList.append(u", ");
|
||||||
|
}
|
||||||
|
missingList.append(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return missingList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkMissingScopes(const QString &token)
|
||||||
|
{
|
||||||
|
NetworkRequest(u"https://id.twitch.tv/oauth2/validate"_s,
|
||||||
|
NetworkRequestType::Get)
|
||||||
|
.header("Authorization", u"OAuth " % token)
|
||||||
|
.timeout(20000)
|
||||||
|
.onSuccess([](const auto &res) {
|
||||||
|
auto *app = tryGetApp();
|
||||||
|
if (!app)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto json = res.parseJson();
|
||||||
|
auto missing = missingScopes(json["scopes"_L1].toArray());
|
||||||
|
if (missing.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = MessageBuilder::makeMissingScopesMessage(missing);
|
||||||
|
app->getTwitch()->forEachChannel([msg](const auto &chan) {
|
||||||
|
chan->addMessage(msg, MessageContext::Original);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.onError([](const auto &res) {
|
||||||
|
qCWarning(chatterinoTwitch)
|
||||||
|
<< "Failed to check for missing scopes:" << res.formatError();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
const std::vector<QStringView> AUTH_SCOPES{
|
||||||
|
u"channel:moderate", // for seeing automod & which moderator banned/unbanned a user (felanbird unbanned weeb123)
|
||||||
|
u"channel:read:redemptions", // for getting the list of channel point redemptions (not currently used)
|
||||||
|
u"chat:edit", // for sending messages in chat
|
||||||
|
u"chat:read", // for viewing messages in chat
|
||||||
|
u"whispers:read", // for viewing recieved whispers
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#start-commercial
|
||||||
|
u"channel:edit:commercial", // for /commercial api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#create-clip
|
||||||
|
u"clips:edit", // for /clip creation
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#create-stream-marker
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#modify-channel-information
|
||||||
|
u"channel:manage:broadcast", // for creating stream markers with /marker command, and for the /settitle and /setgame commands
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-user-block-list
|
||||||
|
u"user:read:blocked_users", // for getting list of blocked users
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#block-user
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#unblock-user
|
||||||
|
u"user:manage:blocked_users", // for blocking/unblocking other users
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages
|
||||||
|
u"moderator:manage:automod", // for approving/denying automod messages
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#start-a-raid
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#cancel-a-raid
|
||||||
|
u"channel:manage:raids", // for starting/canceling raids
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#create-poll
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#end-poll
|
||||||
|
u"channel:manage:polls", // for creating & ending polls (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-polls
|
||||||
|
u"channel:read:polls", // for reading broadcaster poll status (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#create-prediction
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#end-prediction
|
||||||
|
u"channel:manage:predictions", // for creating & ending predictions (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-predictions
|
||||||
|
u"channel:read:predictions", // for reading broadcaster prediction status (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#send-chat-announcement
|
||||||
|
u"moderator:manage:announcements", // for /announce api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#send-whisper
|
||||||
|
u"user:manage:whispers", // for whispers api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#ban-user
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#unban-user
|
||||||
|
u"moderator:manage:banned_users", // for ban/unban/timeout/untimeout api & channel.moderate eventsub topic
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#delete-chat-messages
|
||||||
|
u"moderator:manage:chat_messages", // for delete message api (/delete, /clear) & channel.moderate eventsub topic
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#update-user-chat-color
|
||||||
|
u"user:manage:chat_color", // for update user color api (/color coral)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-chat-settings
|
||||||
|
u"moderator:manage:chat_settings", // for roomstate api (/followersonly, /uniquechat, /slow) & channel.moderate eventsub topic
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-moderators
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#add-channel-moderator
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#remove-channel-vip
|
||||||
|
u"channel:manage:moderators", // for add/remove/view mod api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#add-channel-vip
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#remove-channel-vip
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-vips
|
||||||
|
u"channel:manage:vips", // for add/remove/view vip api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-chatters
|
||||||
|
u"moderator:read:chatters", // for get chatters api
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-shield-mode-status
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#update-shield-mode-status
|
||||||
|
u"moderator:manage:shield_mode", // for reading/managing the channel's shield-mode status
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#send-a-shoutout
|
||||||
|
u"moderator:manage:shoutouts", // for reading/managing the channel's shoutouts (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#get-moderated-channels
|
||||||
|
u"user:read:moderated_channels", // for reading where the user is modded (not currently used)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatmessage
|
||||||
|
u"user:read:chat", // for reading chat via eventsub (in progress)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#send-chat-message
|
||||||
|
u"user:write:chat", // for sending chat messages via helix (in testing)
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#get-user-emotes
|
||||||
|
u"user:read:emotes", // for fetching emotes that a user can use via helix
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#warn-chat-user
|
||||||
|
u"moderator:manage:warnings", // for /warn api & channel.moderate eventsub topic
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference/#get-followed-channels
|
||||||
|
u"user:read:follows", // for determining if the current user follows a streamer
|
||||||
|
|
||||||
|
u"moderator:manage:blocked_terms", // for channel.moderate eventsub topic
|
||||||
|
|
||||||
|
u"moderator:manage:unban_requests", // for channel.moderate eventsub topic
|
||||||
|
|
||||||
|
u"moderator:read:moderators", // for channel.moderate eventsub topic
|
||||||
|
|
||||||
|
u"moderator:read:vips", // for channel.moderate eventsub topic
|
||||||
|
|
||||||
|
u"moderator:read:suspicious_users", // for channel.suspicious_user.message and channel.suspicious_user.update
|
||||||
|
};
|
||||||
|
|
||||||
TwitchAccountManager::TwitchAccountManager()
|
TwitchAccountManager::TwitchAccountManager()
|
||||||
: accounts(SharedPtrElementLess<TwitchAccount>{})
|
: accounts(SharedPtrElementLess<TwitchAccount>{})
|
||||||
, anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
, anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
||||||
@@ -20,6 +203,10 @@ TwitchAccountManager::TwitchAccountManager()
|
|||||||
auto currentUser = this->getCurrent();
|
auto currentUser = this->getCurrent();
|
||||||
currentUser->loadBlocks();
|
currentUser->loadBlocks();
|
||||||
currentUser->loadSeventvUserID();
|
currentUser->loadSeventvUserID();
|
||||||
|
if (!currentUser->isAnon())
|
||||||
|
{
|
||||||
|
checkMissingScopes(currentUser->getOAuthToken());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// We can safely ignore this signal connection since accounts will always be removed
|
// We can safely ignore this signal connection since accounts will always be removed
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace chatterino {
|
|||||||
class TwitchAccount;
|
class TwitchAccount;
|
||||||
class AccountController;
|
class AccountController;
|
||||||
|
|
||||||
|
extern const std::vector<QStringView> AUTH_SCOPES;
|
||||||
|
|
||||||
class TwitchAccountManager
|
class TwitchAccountManager
|
||||||
{
|
{
|
||||||
TwitchAccountManager();
|
TwitchAccountManager();
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QLatin1String>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringView>
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
|
||||||
# include <QUtf8StringView>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
/// Case insensitive transparent comparator for Qt's string types
|
|
||||||
struct QCompareCaseInsensitive {
|
|
||||||
using is_transparent = void;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
bool operator()(const QString & a, const QString & b) const noexcept;
|
|
||||||
bool operator()(QStringView a, QStringView b) const noexcept;
|
|
||||||
bool operator()(QLatin1String a, QLatin1String b) const noexcept;
|
|
||||||
|
|
||||||
bool operator()(const QString & a, QStringView b) const noexcept;
|
|
||||||
bool operator()(const QString & a, QLatin1String b) const noexcept;
|
|
||||||
|
|
||||||
bool operator()(QStringView a, const QString & b) const noexcept;
|
|
||||||
bool operator()(QLatin1String a, const QString & b) const noexcept;
|
|
||||||
|
|
||||||
bool operator()(QStringView a, QLatin1String b) const noexcept;
|
|
||||||
bool operator()(QLatin1String a, QStringView b) const noexcept;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
|
||||||
bool operator()(QUtf8StringView a, QUtf8StringView b) const noexcept;
|
|
||||||
|
|
||||||
bool operator()(const QString & a, QUtf8StringView b) const noexcept;
|
|
||||||
bool operator()(QStringView a, QUtf8StringView b) const noexcept;
|
|
||||||
bool operator()(QLatin1String a, QUtf8StringView b) const noexcept;
|
|
||||||
|
|
||||||
bool operator()(QUtf8StringView a, const QString & b) const noexcept;
|
|
||||||
bool operator()(QUtf8StringView a, QStringView b) const noexcept;
|
|
||||||
bool operator()(QUtf8StringView a, QLatin1String b) const noexcept;
|
|
||||||
#endif
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(const QString &a,
|
|
||||||
const QString &b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QStringView a,
|
|
||||||
QStringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QLatin1String a,
|
|
||||||
QLatin1String b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(const QString &a,
|
|
||||||
QStringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(const QString &a,
|
|
||||||
QLatin1String b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QStringView a,
|
|
||||||
const QString &b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QLatin1String a,
|
|
||||||
const QString &b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QStringView a,
|
|
||||||
QLatin1String b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QLatin1String a,
|
|
||||||
QStringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(
|
|
||||||
QUtf8StringView a, QUtf8StringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(
|
|
||||||
const QString &a, QUtf8StringView b) const noexcept
|
|
||||||
{
|
|
||||||
return QStringView{a}.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(
|
|
||||||
QStringView a, QUtf8StringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(
|
|
||||||
QLatin1String a, QUtf8StringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QUtf8StringView a,
|
|
||||||
const QString &b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QUtf8StringView a,
|
|
||||||
QStringView b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool QCompareCaseInsensitive::operator()(QUtf8StringView a,
|
|
||||||
QLatin1String b) const noexcept
|
|
||||||
{
|
|
||||||
return a.compare(b, Qt::CaseInsensitive) < 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QLatin1String>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringView>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
|
# include <QUtf8StringView>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
struct QCompareTransparentBase {
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
bool operator()(const QString & a, const QString & b) const noexcept;
|
||||||
|
bool operator()(QStringView a, QStringView b) const noexcept;
|
||||||
|
bool operator()(QLatin1String a, QLatin1String b) const noexcept;
|
||||||
|
|
||||||
|
bool operator()(const QString & a, QStringView b) const noexcept;
|
||||||
|
bool operator()(const QString & a, QLatin1String b) const noexcept;
|
||||||
|
|
||||||
|
bool operator()(QStringView a, const QString & b) const noexcept;
|
||||||
|
bool operator()(QLatin1String a, const QString & b) const noexcept;
|
||||||
|
|
||||||
|
bool operator()(QStringView a, QLatin1String b) const noexcept;
|
||||||
|
bool operator()(QLatin1String a, QStringView b) const noexcept;
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
|
bool operator()(QUtf8StringView a, QUtf8StringView b) const noexcept;
|
||||||
|
|
||||||
|
bool operator()(const QString & a, QUtf8StringView b) const noexcept;
|
||||||
|
bool operator()(QStringView a, QUtf8StringView b) const noexcept;
|
||||||
|
bool operator()(QLatin1String a, QUtf8StringView b) const noexcept;
|
||||||
|
|
||||||
|
bool operator()(QUtf8StringView a, const QString & b) const noexcept;
|
||||||
|
bool operator()(QUtf8StringView a, QStringView b) const noexcept;
|
||||||
|
bool operator()(QUtf8StringView a, QLatin1String b) const noexcept;
|
||||||
|
#endif
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Case insensitive transparent comparator for Qt's string types
|
||||||
|
using QCompareCaseInsensitive = QCompareTransparentBase<Qt::CaseInsensitive>;
|
||||||
|
|
||||||
|
/// Case sensitive transparent comparator for Qt's string types
|
||||||
|
using QCompareTransparent = QCompareTransparentBase<Qt::CaseSensitive>;
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
const QString &a, const QString &b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QStringView a, QStringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QLatin1String a, QLatin1String b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
const QString &a, QStringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
const QString &a, QLatin1String b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QStringView a, const QString &b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QLatin1String a, const QString &b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QStringView a, QLatin1String b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QLatin1String a, QStringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QUtf8StringView a, QUtf8StringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
const QString &a, QUtf8StringView b) const noexcept
|
||||||
|
{
|
||||||
|
return QStringView{a}.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QStringView a, QUtf8StringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QLatin1String a, QUtf8StringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QUtf8StringView a, const QString &b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QUtf8StringView a, QStringView b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Qt::CaseSensitivity CS>
|
||||||
|
inline bool QCompareTransparentBase<CS>::operator()(
|
||||||
|
QUtf8StringView a, QLatin1String b) const noexcept
|
||||||
|
{
|
||||||
|
return a.compare(b, CS) < 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
#include "mocks/TwitchIrcServer.hpp"
|
#include "mocks/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/eventsub/Connection.hpp"
|
#include "providers/twitch/eventsub/Connection.hpp"
|
||||||
#include "Test.hpp"
|
#include "Test.hpp"
|
||||||
#include "util/QCompareCaseInsensitive.hpp"
|
#include "util/QCompareTransparent.hpp"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user