Added subage and followage to usercard (#2023)

* Added subage and followage information to usercard

We are using Leppunen's API here to determine user's subage to the current channel and since that API call also returns followage information I decided to utilize that and save ourselves an extra Helix API call.
I also added new files specifying new class and methods for Ivr API, which can be very easily expanded with new methods in the future if we ever have to do that.
When I was coding I also saw couple unnecessary nitpicks which I fixed :)

* Added changelog entry

* remove empty lambda

* Update UserInfoPopup.cpp

* xd

Co-authored-by: fourtf <tf.four@gmail.com>
This commit is contained in:
Paweł 2020-10-04 18:32:52 +02:00 committed by GitHub
parent 35816c5d8a
commit 776ce8bdbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 9 deletions

View file

@ -2,6 +2,7 @@
## Unversioned
- Minor: Added followage and subage information to usercard. (#2023)
- Minor: Added an option to only open channels specified in command line with `-c` parameter. You can also use `--help` to display short help message (#1940)
- Minor: Added customizable timeout buttons to the user info popup
- Minor: Deprecate loading of "v1" window layouts. If you haven't updated Chatterino in more than 2 years, there's a chance you will lose your window layout.

View file

@ -175,6 +175,7 @@ SOURCES += \
src/providers/irc/IrcConnection2.cpp \
src/providers/irc/IrcMessageBuilder.cpp \
src/providers/irc/IrcServer.cpp \
src/providers/IvrApi.cpp \
src/providers/LinkResolver.cpp \
src/providers/twitch/ChannelPointReward.cpp \
src/providers/twitch/api/Helix.cpp \
@ -389,6 +390,7 @@ HEADERS += \
src/providers/irc/IrcConnection2.hpp \
src/providers/irc/IrcMessageBuilder.hpp \
src/providers/irc/IrcServer.hpp \
src/providers/IvrApi.hpp \
src/providers/LinkResolver.hpp \
src/providers/twitch/ChannelPointReward.hpp \
src/providers/twitch/api/Helix.hpp \

View file

@ -10,6 +10,7 @@
#include "common/Args.hpp"
#include "common/Modes.hpp"
#include "common/Version.hpp"
#include "providers/IvrApi.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/api/Kraken.hpp"
#include "singletons/Paths.hpp"
@ -45,6 +46,7 @@ int main(int argc, char **argv)
}
else
{
IvrApi::initialize();
Helix::initialize();
Kraken::initialize();

59
src/providers/IvrApi.cpp Normal file
View file

@ -0,0 +1,59 @@
#include "IvrApi.hpp"
#include "common/Outcome.hpp"
#include <QUrlQuery>
namespace chatterino {
static IvrApi *instance = nullptr;
void IvrApi::getSubage(QString userName, QString channelName,
ResultCallback<IvrSubage> successCallback,
IvrFailureCallback failureCallback)
{
assert(!userName.isEmpty() && !channelName.isEmpty());
this->makeRequest("twitch/subage/" + userName + "/" + channelName, {})
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
auto root = result.parseJson();
successCallback(root);
return Success;
})
.onError([failureCallback](NetworkResult result) {
qDebug() << "Failed IVR API Call!" << result.status()
<< QString(result.getData());
failureCallback();
})
.execute();
}
NetworkRequest IvrApi::makeRequest(QString url, QUrlQuery urlQuery)
{
assert(!url.startsWith("/"));
const QString baseUrl("https://api.ivr.fi/");
QUrl fullUrl(baseUrl + url);
fullUrl.setQuery(urlQuery);
return NetworkRequest(fullUrl).timeout(5 * 1000).header("Accept",
"application/json");
}
void IvrApi::initialize()
{
assert(instance == nullptr);
instance = new IvrApi();
}
IvrApi *getIvr()
{
assert(instance != nullptr);
return instance;
}
} // namespace chatterino

48
src/providers/IvrApi.hpp Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "common/NetworkRequest.hpp"
#include "messages/Link.hpp"
#include <functional>
namespace chatterino {
using IvrFailureCallback = std::function<void()>;
template <typename... T>
using ResultCallback = std::function<void(T...)>;
struct IvrSubage {
const bool isSubHidden;
const bool isSubbed;
const QString subTier;
const int totalSubMonths;
const QString followingSince;
IvrSubage(QJsonObject root)
: isSubHidden(root.value("hidden").toBool())
, isSubbed(root.value("subscribed").toBool())
, subTier(root.value("meta").toObject().value("tier").toString())
, totalSubMonths(
root.value("cumulative").toObject().value("months").toInt())
, followingSince(root.value("followedAt").toString())
{
}
};
class IvrApi final : boost::noncopyable
{
public:
// https://api.ivr.fi/docs#tag/Twitch/paths/~1twitch~1subage~1{username}~1{channel}/get
void getSubage(QString userName, QString channelName,
ResultCallback<IvrSubage> resultCallback,
IvrFailureCallback failureCallback);
static void initialize();
private:
NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
};
IvrApi *getIvr();
} // namespace chatterino

View file

@ -52,12 +52,12 @@ void Helix::fetchUsers(QStringList userIds, QStringList userLogins,
.execute();
}
void Helix::getUserByName(QString userId,
void Helix::getUserByName(QString userName,
ResultCallback<HelixUser> successCallback,
HelixFailureCallback failureCallback)
{
QStringList userIds;
QStringList userLogins{userId};
QStringList userLogins{userName};
this->fetchUsers(
userIds, userLogins,

View file

@ -5,7 +5,6 @@
#include <QString>
#include <QStringList>
#include <QUrlQuery>
#include <boost/noncopyable.hpp>
#include <functional>

View file

@ -6,6 +6,7 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightBlacklistUser.hpp"
#include "messages/Message.hpp"
#include "providers/IvrApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/api/Kraken.hpp"
@ -140,9 +141,9 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically)
QUrl("https://twitch.tv/" + this->userName_.toLower()));
});
// items on the right
auto vbox = head.emplace<QVBoxLayout>();
{
// items on the right
{
auto box = vbox.emplace<QHBoxLayout>()
.withoutMargin()
@ -150,18 +151,23 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically)
this->ui_.nameLabel = addCopyableLabel(box);
this->ui_.nameLabel->setFontStyle(FontStyle::UiMediumBold);
box->addStretch(1);
this->ui_.userIDLabel = addCopyableLabel(box);
auto palette = QPalette();
palette.setColor(QPalette::WindowText, QColor("#aaa"));
this->ui_.userIDLabel = addCopyableLabel(box);
this->ui_.userIDLabel->setPalette(palette);
}
// items on the left
vbox.emplace<Label>(TEXT_VIEWS.arg(""))
.assign(&this->ui_.viewCountLabel);
vbox.emplace<Label>(TEXT_FOLLOWERS.arg(""))
.assign(&this->ui_.followerCountLabel);
vbox.emplace<Label>(TEXT_CREATED.arg(""))
.assign(&this->ui_.createdDateLabel);
vbox.emplace<Line>(true);
vbox.emplace<Label>("")
.assign(&this->ui_.followageSubageLabel)
->setMinimumSize(this->minimumSizeHint());
}
}
@ -533,6 +539,7 @@ void UserInfoPopup::updateUserData()
this->ui_.follow->setEnabled(false);
std::weak_ptr<bool> hack = this->hack_;
auto currentUser = getApp()->accounts->twitch.getCurrent();
const auto onUserFetchFailed = [this, hack] {
if (!hack.lock())
@ -548,19 +555,18 @@ void UserInfoPopup::updateUserData()
this->ui_.nameLabel->setText(this->userName_);
this->ui_.userIDLabel->setText(QString("ID") +
this->ui_.userIDLabel->setText(QString("ID ") +
QString(TEXT_UNAVAILABLE));
this->ui_.userIDLabel->setProperty("copy-text",
QString(TEXT_UNAVAILABLE));
};
const auto onUserFetched = [this, hack](const auto &user) {
const auto onUserFetched = [this, hack,
currentUser](const HelixUser &user) {
if (!hack.lock())
{
return;
}
auto currentUser = getApp()->accounts->twitch.getCurrent();
this->userId_ = user.id;
this->ui_.userIDLabel->setText(TEXT_USER_ID + user.id);
@ -650,6 +656,46 @@ void UserInfoPopup::updateUserData()
this->ui_.ignore->setEnabled(true);
this->ui_.ignore->setChecked(isIgnoring);
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
// get followage and subage
getIvr()->getSubage(
this->userName_, this->channel_->getName(),
[this, hack](const IvrSubage &subageInfo) {
if (!hack.lock())
{
return;
}
QString labelText;
if (!subageInfo.followingSince.isEmpty())
{
QDateTime followedAt = QDateTime::fromString(
subageInfo.followingSince, Qt::ISODate);
QString followingSince = followedAt.toString("yyyy-MM-dd");
labelText = "Following since " + followingSince;
}
if (subageInfo.isSubHidden)
{
labelText += "\nSubscribtion status hidden";
}
if (subageInfo.isSubbed)
{
labelText += QString("\nTier %1 - Subscribed for %2 months")
.arg(subageInfo.subTier)
.arg(subageInfo.totalSubMonths);
}
else if (subageInfo.totalSubMonths)
{
labelText +=
QString("\nPreviously subscribed for %1 months")
.arg(subageInfo.totalSubMonths);
}
this->ui_.followageSubageLabel->setText(labelText);
},
[] {});
};
getHelix()->getUserByName(this->userName_, onUserFetched,

View file

@ -55,6 +55,7 @@ private:
Label *followerCountLabel = nullptr;
Label *createdDateLabel = nullptr;
Label *userIDLabel = nullptr;
Label *followageSubageLabel = nullptr;
QCheckBox *follow = nullptr;
QCheckBox *ignore = nullptr;