summaryrefslogtreecommitdiff
path: root/src/nexusinterface.cpp
diff options
context:
space:
mode:
authorSilarn <jrim@rimpo.org>2019-07-22 01:00:42 -0500
committerSilarn <jrim@rimpo.org>2019-07-22 01:00:42 -0500
commitdcd6d624672019727d7effd17aac86f72bff438b (patch)
tree1e8d3856f657d898c5992631599cf272d785f973 /src/nexusinterface.cpp
parent179a73857125ee604f42b0d5c2d765183c86d2c7 (diff)
parente73c309f08eff98f0dbd2590f594a83b67431eac (diff)
Merge branch 'Develop'
Diffstat (limited to 'src/nexusinterface.cpp')
-rw-r--r--src/nexusinterface.cpp259
1 files changed, 150 insertions, 109 deletions
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index 9db0d54e..ee9acf2c 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -38,6 +38,17 @@ using namespace MOBase;
using namespace MOShared;
+void throttledWarning(const APIUserAccount& user)
+{
+ qCritical() <<
+ QString(
+ "You have fewer than %1 requests remaining (%2). Only downloads and "
+ "login validation are being allowed.")
+ .arg(APIUserAccount::ThrottleThreshold)
+ .arg(user.remainingRequests());
+}
+
+
NexusBridge::NexusBridge(PluginContainer *pluginContainer, const QString &subModule)
: m_Interface(NexusInterface::instance(pluginContainer))
, m_SubModule(subModule)
@@ -179,9 +190,39 @@ void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVar
QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0);
+APILimits NexusInterface::defaultAPILimits()
+{
+ // https://app.swaggerhub.com/apis-docs/NexusMods/nexus-mods_public_api_params_in_form_data/1.0#/
+ const int MaxDaily = 2500;
+ const int MaxHourly = 100;
+
+ APILimits limits;
+
+ limits.maxDailyRequests = MaxDaily;
+ limits.remainingDailyRequests = MaxDaily;
+ limits.maxHourlyRequests = MaxHourly;
+ limits.remainingHourlyRequests = MaxHourly;
+
+ return limits;
+}
+
+APILimits NexusInterface::parseLimits(const QNetworkReply* reply)
+{
+ APILimits limits;
+
+ limits.maxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
+ limits.remainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
+ limits.maxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
+ limits.remainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
+
+ return limits;
+}
+
+
NexusInterface::NexusInterface(PluginContainer *pluginContainer)
- : m_PluginContainer(pluginContainer), m_RemainingDailyRequests(2500), m_RemainingHourlyRequests(100), m_MaxDailyRequests(2500), m_MaxHourlyRequests(100)
+ : m_PluginContainer(pluginContainer)
{
+ m_User.limits(defaultAPILimits());
m_MOVersion = createVersionInfo();
m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3));
@@ -216,13 +257,10 @@ void NexusInterface::loginCompleted()
nextRequest();
}
-void NexusInterface::setRateMax(const QString&, bool, std::tuple<int, int, int, int> limits)
+void NexusInterface::setUserAccount(const APIUserAccount& user)
{
- m_RemainingDailyRequests = std::get<0>(limits);
- m_MaxDailyRequests = std::get<1>(limits);
- m_RemainingHourlyRequests = std::get<2>(limits);
- m_MaxHourlyRequests = std::get<3>(limits);
- emit requestsChanged(m_RequestQueue.size(), limits);
+ m_User = user;
+ emit requestsChanged(getAPIStats(), m_User);
}
void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query)
@@ -361,70 +399,70 @@ int NexusInterface::requestDescription(QString gameName, int modID, QObject *rec
int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData,
const QString &subModule, const MOBase::IPluginGame *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)),
- receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)),
+ receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData,
QString gameName, const QString &subModule)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- IPluginGame *game = getGame(gameName);
- if (game == nullptr) {
- qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName));
- return -1;
- }
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game);
- m_RequestQueue.enqueue(requestInfo);
+ IPluginGame *game = getGame(gameName);
+ if (game == nullptr) {
+ qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName));
+ return -1;
+ }
- connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game);
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
@@ -519,23 +557,23 @@ int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData,
int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game);
- requestInfo.m_Endorse = endorse;
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
- receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game);
+ requestInfo.m_Endorse = endorse;
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)),
+ receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule)
@@ -556,23 +594,23 @@ int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, co
int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData,
const QString &subModule, MOBase::IPluginGame const *game)
{
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) {
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game);
- requestInfo.m_Track = track;
- m_RequestQueue.enqueue(requestInfo);
+ if (m_User.shouldThrottle()) {
+ throttledWarning(m_User);
+ return -1;
+ }
- connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)),
- receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection);
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game);
+ requestInfo.m_Track = track;
+ m_RequestQueue.enqueue(requestInfo);
- connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
- receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+ connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)),
+ receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection);
- nextRequest();
- return requestInfo.m_ID;
- }
- qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.")
- .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests));
- return -1;
+ connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)),
+ receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
}
int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData,
@@ -633,16 +671,16 @@ void NexusInterface::nextRequest()
} else if (getAccessManager()->validateWaiting()) {
return;
} else {
- qCritical("You must authorize MO2 in Settings -> Nexus to use the Nexus API.");
+ qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.");
}
}
- if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) <= 0) {
+ if (m_User.exhausted()) {
m_RequestQueue.clear();
QTime time = QTime::currentTime();
QTime targetTime;
targetTime.setHMS((time.hour() + 1) % 23, 5, 0);
- QString warning("You've exceeded the Nexus API rate limit and requests are now being throttled. "
+ QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. "
"Your next batch of requests will be available in approximately %1 minutes and %2 seconds.");
qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60);
return;
@@ -688,11 +726,16 @@ void NexusInterface::nextRequest()
} break;
case NXMRequestInfo::TYPE_DOWNLOADURL: {
ModRepositoryFileInfo *fileInfo = qobject_cast<ModRepositoryFileInfo*>(qvariant_cast<QObject*>(info.m_UserData));
- if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires)
+ if (m_User.type() == APIUserAccountTypes::Premium) {
+ url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID);
+ } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_User.id().toInt()) {
url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6")
.arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires);
- else
- url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID);
+ } else {
+ qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, "
+ "or the download link was generated by a different account than the one stored in Mod Organizer.");
+ return;
+ }
} break;
case NXMRequestInfo::TYPE_ENDORSEMENTS: {
url = QString("%1/user/endorsements").arg(info.m_URL);
@@ -767,23 +810,16 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
if (iter->m_AllowedErrors.contains(error) && iter->m_AllowedErrors[error].contains(statusCode)) {
// These errors are allows to silently happen. They should be handled in nxmRequestFailed below.
} else if (statusCode == 429) {
- m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
- m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
- m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
- m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
+ m_User.limits(parseLimits(reply));
- if (m_RemainingDailyRequests || m_RemainingHourlyRequests)
+ if (!m_User.exhausted()) {
qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team.");
- else
+ }
+ else {
qWarning("All API requests have been consumed and are now being denied.");
+ }
- emit requestsChanged(m_RequestQueue.size(), std::tuple<int, int, int, int>(std::make_tuple(
- m_RemainingDailyRequests,
- m_MaxDailyRequests,
- m_RemainingHourlyRequests,
- m_MaxHourlyRequests
- )));
-
+ emit requestsChanged(getAPIStats(), m_User);
qWarning("Error: %s", reply->errorString().toUtf8().constData());
} else {
qWarning("request failed: %s", reply->errorString().toUtf8().constData());
@@ -858,17 +894,8 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
} break;
}
- m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt();
- m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt();
- m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt();
- m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt();
-
- emit requestsChanged(m_RequestQueue.size(), std::tuple<int, int, int, int>(std::make_tuple(
- m_RemainingDailyRequests,
- m_MaxDailyRequests,
- m_RemainingHourlyRequests,
- m_MaxHourlyRequests
- )));
+ m_User.limits(parseLimits(reply));
+ emit requestsChanged(getAPIStats(), m_User);
} else {
emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), tr("invalid response"));
}
@@ -925,6 +952,20 @@ void NexusInterface::requestTimeout()
}
}
+APIUserAccount NexusInterface::getAPIUserAccount() const
+{
+ return m_User;
+}
+
+APIStats NexusInterface::getAPIStats() const
+{
+ APIStats stats;
+ stats.requestsQueued = m_RequestQueue.size();
+
+ return stats;
+}
+
+
namespace {
QString get_management_url()
{