feat: add setting for showing pronouns in user info popup (#5442)

This uses https://pr.alejo.io/

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Timo Zuccarello
2024-09-08 11:19:43 +02:00
committed by GitHub
parent aae1288112
commit 9375bce555
18 changed files with 402 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
#include "providers/pronouns/Pronouns.hpp"
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "providers/pronouns/alejo/PronounsAlejoApi.hpp"
#include "providers/pronouns/UserPronouns.hpp"
#include <mutex>
#include <string>
#include <unordered_map>
namespace chatterino::pronouns {
void Pronouns::fetch(const QString &username,
const std::function<void(UserPronouns)> &callbackSuccess,
const std::function<void()> &callbackFail)
{
// Only fetch pronouns if we haven't fetched before.
{
std::shared_lock lock(this->mutex);
auto iter = this->saved.find(username);
if (iter != this->saved.end())
{
callbackSuccess(iter->second);
return;
}
} // unlock mutex
qCDebug(chatterinoPronouns)
<< "Fetching pronouns from alejo.io for " << username;
alejoApi.fetch(username, [this, callbackSuccess, callbackFail,
username](std::optional<UserPronouns> result) {
if (result.has_value())
{
{
std::unique_lock lock(this->mutex);
this->saved[username] = *result;
} // unlock mutex
qCDebug(chatterinoPronouns)
<< "Adding pronouns " << result->format() << " for user "
<< username;
callbackSuccess(*result);
}
else
{
callbackFail();
}
});
}
std::optional<UserPronouns> Pronouns::getForUsername(const QString &username)
{
std::shared_lock lock(this->mutex);
auto it = this->saved.find(username);
if (it != this->saved.end())
{
return {it->second};
}
return {};
}
} // namespace chatterino::pronouns
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "providers/pronouns/alejo/PronounsAlejoApi.hpp"
#include "providers/pronouns/UserPronouns.hpp"
#include <optional>
#include <shared_mutex>
#include <unordered_map>
namespace chatterino::pronouns {
class Pronouns
{
public:
Pronouns() = default;
void fetch(const QString &username,
const std::function<void(UserPronouns)> &callbackSuccess,
const std::function<void()> &callbackFail);
// Retrieve cached pronouns for user.
std::optional<UserPronouns> getForUsername(const QString &username);
private:
// mutex for editing the saved map.
std::shared_mutex mutex;
// Login name -> Pronouns
std::unordered_map<QString, UserPronouns> saved;
AlejoApi alejoApi;
};
} // namespace chatterino::pronouns
+24
View File
@@ -0,0 +1,24 @@
#include "providers/pronouns/UserPronouns.hpp"
#include <QString>
#include <optional>
namespace chatterino::pronouns {
UserPronouns::UserPronouns(QString pronouns)
: representation{!pronouns.isEmpty() ? std::move(pronouns) : QString()}
{
}
bool UserPronouns::isUnspecified() const
{
return this->representation.isEmpty();
}
UserPronouns::operator bool() const
{
return !isUnspecified();
}
} // namespace chatterino::pronouns
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <QString>
namespace chatterino::pronouns {
class UserPronouns
{
public:
UserPronouns() = default;
UserPronouns(QString pronouns);
QString format() const
{
if (isUnspecified())
{
return "unspecified";
}
return this->representation;
}
bool isUnspecified() const;
/// True, iff the pronouns are not unspecified.
operator bool() const;
private:
QString representation;
};
} // namespace chatterino::pronouns
@@ -0,0 +1,124 @@
#include "providers/pronouns/alejo/PronounsAlejoApi.hpp"
#include "common/network/NetworkRequest.hpp"
#include "common/network/NetworkResult.hpp"
#include "common/QLogging.hpp"
#include "providers/pronouns/UserPronouns.hpp"
namespace chatterino::pronouns {
UserPronouns AlejoApi::parse(const QJsonObject &object)
{
if (!this->pronounsFromId.has_value())
{
return {};
}
auto pronoun = object["pronoun_id"];
if (!pronoun.isString())
{
return {};
}
auto pronounStr = pronoun.toString();
std::shared_lock lock(this->mutex);
auto iter = this->pronounsFromId->find(pronounStr);
if (iter != this->pronounsFromId->end())
{
return {iter->second};
}
return {};
}
AlejoApi::AlejoApi()
{
std::shared_lock lock(this->mutex);
if (this->pronounsFromId)
{
return;
}
qCDebug(chatterinoPronouns) << "Fetching available pronouns for alejo.io";
NetworkRequest(AlejoApi::API_URL + AlejoApi::API_PRONOUNS)
.concurrent()
.onSuccess([this](const auto &result) {
auto object = result.parseJson();
if (object.isEmpty())
{
return;
}
std::unique_lock lock(this->mutex);
this->pronounsFromId = {std::unordered_map<QString, QString>()};
for (auto const &pronounId : object.keys())
{
if (!object[pronounId].isObject())
{
continue;
};
const auto pronounObj = object[pronounId].toObject();
if (!pronounObj["subject"].isString())
{
continue;
}
QString pronouns = pronounObj["subject"].toString();
auto singular = pronounObj["singular"];
if (singular.isBool() && !singular.toBool() &&
pronounObj["object"].isString())
{
pronouns += "/" + pronounObj["object"].toString();
}
this->pronounsFromId->insert_or_assign(pronounId,
pronouns.toLower());
}
})
.execute();
}
void AlejoApi::fetch(const QString &username,
std::function<void(std::optional<UserPronouns>)> onDone)
{
bool havePronounList{true};
{
std::shared_lock lock(this->mutex);
havePronounList = this->pronounsFromId.has_value();
} // unlock mutex
if (!havePronounList)
{
// Pronoun list not available yet, just fail and try again next time.
onDone({});
return;
}
NetworkRequest(AlejoApi::API_URL + AlejoApi::API_USERS + "/" + username)
.concurrent()
.onSuccess([this, username, onDone](const auto &result) {
auto object = result.parseJson();
auto parsed = this->parse(object);
onDone({parsed});
})
.onError([onDone, username](auto result) {
auto status = result.status();
if (status.has_value() && status == 404)
{
// Alejo returns 404 if the user has no pronouns set.
// Return unspecified.
onDone({UserPronouns()});
return;
}
qCWarning(chatterinoPronouns)
<< "alejo.io returned " << status.value_or(-1)
<< " when fetching pronouns for " << username;
onDone({});
})
.execute();
}
} // namespace chatterino::pronouns
@@ -0,0 +1,35 @@
#pragma once
#include "providers/pronouns/UserPronouns.hpp"
#include <QJsonObject>
#include <QString>
#include <mutex>
#include <optional>
#include <shared_mutex>
namespace chatterino::pronouns {
class AlejoApi
{
public:
explicit AlejoApi();
/** Fetches pronouns from the alejo.io API for a username and calls onDone when done.
onDone can be invoked from any thread. The argument is std::nullopt if and only if the request failed.
*/
void fetch(const QString &username,
std::function<void(std::optional<UserPronouns>)> onDone);
private:
std::shared_mutex mutex;
/** A map from alejo.io ids to human readable representation like theythem -> they/them, other -> other. */
std::optional<std::unordered_map<QString, QString>> pronounsFromId =
std::nullopt;
UserPronouns parse(const QJsonObject &object);
inline static const QString API_URL = "https://api.pronouns.alejo.io/v1";
inline static const QString API_USERS = "/users";
inline static const QString API_PRONOUNS = "/pronouns";
};
} // namespace chatterino::pronouns