diff options
| -rw-r--r-- | src/main.cpp | 3 | ||||
| -rw-r--r-- | src/nexusinterface.cpp | 14 | ||||
| -rw-r--r-- | src/nxmaccessmanager.cpp | 171 | ||||
| -rw-r--r-- | src/nxmaccessmanager.h | 40 | ||||
| -rw-r--r-- | src/organizercore.cpp | 51 | ||||
| -rw-r--r-- | src/organizercore.h | 2 |
6 files changed, 215 insertions, 66 deletions
diff --git a/src/main.cpp b/src/main.cpp index ae3bbb4c..8e9b7c9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,6 +62,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "selectiondialog.h"
#include "moapplication.h"
#include "tutorialmanager.h"
+#include "nxmaccessmanager.h"
#include <iostream>
#include <QMessageBox>
#include <QSharedMemory>
@@ -534,6 +535,8 @@ int main(int argc, char *argv[]) }
}
+ NexusInterface::instance()->getAccessManager()->startLoginCheck();
+
qDebug("initializing tutorials");
TutorialManager::init(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
&organizer);
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 5e29e8ee..dc027be4 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -391,8 +391,7 @@ void NexusInterface::nextRequest() return;
}
- if (!getAccessManager()->loggedIn()
- && requiresLogin(m_RequestQueue.head())) {
+ if (requiresLogin(m_RequestQueue.head()) && !getAccessManager()->loggedIn()) {
if (!getAccessManager()->loginAttempted()) {
emit needLogin();
return;
@@ -438,16 +437,7 @@ void NexusInterface::nextRequest() }
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
- QStringList comments;
- comments << "compatible to Nexus Client v" + m_NMMVersion;
- if (!info.m_SubModule.isEmpty()) {
- comments << "module: " + info.m_SubModule;
- }
-
- QString userAgent = QString("Mod Organizer v%1 (%2)")
- .arg(m_MOVersion.displayString())
- .arg(comments.join("; "));
- request.setRawHeader("User-Agent", userAgent.toUtf8());
+ request.setRawHeader("User-Agent", m_AccessManager->userAgent(info.m_SubModule).toUtf8());
info.m_Reply = m_AccessManager->get(request);
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c99d2d8d..0786a254 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -24,6 +24,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "selfupdater.h"
#include "persistentcookiejar.h"
#include "settings.h"
+#include <gameinfo.h>
+#include <json.h>
#include <QMessageBox>
#include <QPushButton>
#include <QNetworkProxy>
@@ -32,24 +34,29 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QNetworkCookieJar>
#include <QCoreApplication>
#include <QDir>
-#include <gameinfo.h>
-
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QUrlQuery>
-#endif
+#include <QThread>
+#include <QJsonDocument>
+#include <QJsonArray>
+
using namespace MOBase;
using namespace MOShared;
+// unfortunately Nexus doesn't seem to document these states, all I know is all these listed
+// are considered premium (27 should be lifetime premium)
+const std::set<int> NXMAccessManager::s_PremiumAccountStates { 4, 6, 13, 27, 31, 32 };
+
+
NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion)
: QNetworkAccessManager(parent)
, m_LoginReply(nullptr)
, m_ProgressDialog()
, m_MOVersion(moVersion)
- , m_LoginAttempted(false)
{
-
+ m_LoginTimeout.setSingleShot(true);
+ m_LoginTimeout.setInterval(30000);
setCookieJar(new PersistentCookieJar(
QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat")));
}
@@ -88,20 +95,96 @@ QNetworkReply *NXMAccessManager::createRequest( }
-void NXMAccessManager::showCookies()
+void NXMAccessManager::showCookies() const
{
- QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/"));
- foreach (QNetworkCookie cookie, cookies) {
- qDebug("%s - %s", cookie.name().constData(), cookie.value().constData());
+ QUrl url(ToQString(GameInfo::instance().getNexusPage()) + "/");
+
+ for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) {
+ qDebug("%s - %s (expires: %s)",
+ cookie.name().constData(), cookie.value().constData(),
+ qPrintable(cookie.expirationDate().toString()));
}
}
+void NXMAccessManager::startLoginCheck()
+{
+ if (hasLoginCookies()) {
+ QNetworkRequest request(ToQString(GameInfo::instance().getNexusPage()) + "/Sessions/?Validate");
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ request.setRawHeader("User-Agent", userAgent().toUtf8());
+
+ m_LoginReply = get(request);
+ m_LoginTimeout.start();
+ m_LoginState = LOGIN_CHECKING;
+ connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginChecked()));
+ connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(loginError(QNetworkReply::NetworkError)));
+ }
+}
+
+
+void NXMAccessManager::retrieveCredentials()
+{
+ QNetworkRequest request(ToQString(GameInfo::instance().getNexusPage())
+ + QString("/Core/Libs/Flamework/Entities/User?GetCredentials&game_id=%1"
+ ).arg(GameInfo::instance().getNexusGameID()));
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ request.setRawHeader("User-Agent", userAgent().toUtf8());
+
+ QNetworkReply *reply = get(request);
+ QTimer timeout;
+ connect(&timeout, &QTimer::timeout, [reply] () {
+ reply->deleteLater();
+ });
+ timeout.start();
+
+ connect(reply, &QNetworkReply::finished, [reply, this] () {
+ QJsonDocument jdoc = QJsonDocument::fromJson(reply->readAll());
+ QJsonArray credentialsData = jdoc.array();
+ emit credentialsReceived(credentialsData.at(2).toString(),
+ s_PremiumAccountStates.find(credentialsData.at(1).toInt())
+ != s_PremiumAccountStates.end());
+ reply->deleteLater();
+ });
+
+ connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
+ [=] (QNetworkReply::NetworkError) {
+ qDebug("failed to retrieve account credentials: %s", qPrintable(reply->errorString()));
+ reply->deleteLater();
+ });
+}
+
+
bool NXMAccessManager::loggedIn() const
{
- return hasLoginCookies();
+ if (m_LoginState == LOGIN_CHECKING) {
+ QProgressDialog progress;
+ progress.setLabelText(tr("Verifying Nexus login"));
+ progress.show();
+ while (m_LoginState == LOGIN_CHECKING) {
+ QCoreApplication::processEvents();
+ QThread::msleep(100);
+ }
+ progress.hide();
+ }
+
+ return m_LoginState == LOGIN_VALID;
+}
+
+
+void NXMAccessManager::refuseLogin()
+{
+ m_LoginState = LOGIN_REFUSED;
}
+
+bool NXMAccessManager::loginAttempted() const
+{
+ return m_LoginState != LOGIN_NOT_CHECKED;
+}
+
+
bool NXMAccessManager::loginWaiting() const
{
return m_LoginReply != nullptr;
@@ -114,19 +197,29 @@ void NXMAccessManager::login(const QString &username, const QString &password) return;
}
- if (hasLoginCookies()) {
+ if (m_LoginState == LOGIN_VALID) {
emit loginSuccessful(false);
return;
}
- m_LoginAttempted = true;
-
m_Username = username;
m_Password = password;
pageLogin();
}
+QString NXMAccessManager::userAgent(const QString &subModule) const
+{
+ QStringList comments;
+ comments << "compatible to Nexus Client v" + m_NMMVersion;
+ if (!subModule.isEmpty()) {
+ comments << "module: " + subModule;
+ }
+
+ return QString("Mod Organizer v%1 (%2)").arg(m_MOVersion, comments.join("; "));
+}
+
+
void NXMAccessManager::pageLogin()
{
QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/Sessions/?Login&uri=%1")
@@ -148,8 +241,7 @@ void NXMAccessManager::pageLogin() postDataQuery = postData.encodedQuery();
#endif
- QString userAgent = QString("Mod Organizer v%1 (compatible to Nexus Client v%2)").arg(m_MOVersion).arg(m_NMMVersion);
- request.setRawHeader("User-Agent", userAgent.toUtf8());
+ request.setRawHeader("User-Agent", userAgent().toUtf8());
m_ProgressDialog.setLabelText(tr("Logging into Nexus"));
QList<QPushButton*> buttons = m_ProgressDialog.findChildren<QPushButton*>();
@@ -159,6 +251,7 @@ void NXMAccessManager::pageLogin() m_LoginReply = post(request, postDataQuery);
m_LoginTimeout.start();
+ m_LoginState = LOGIN_CHECKING;
connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished()));
connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError)));
}
@@ -166,37 +259,41 @@ void NXMAccessManager::pageLogin() void NXMAccessManager::loginTimeout()
{
- emit loginFailed(tr("timeout"));
m_LoginReply->deleteLater();
m_LoginReply = nullptr;
m_LoginAttempted = false; // this usually means we might have success later
- m_LoginTimeout.stop();
m_Username.clear();
m_Password.clear();
+ m_LoginState = LOGIN_NOT_VALID;
+
+ emit loginFailed(tr("timeout"));
}
void NXMAccessManager::loginError(QNetworkReply::NetworkError)
{
+ qDebug("login error");
m_ProgressDialog.hide();
- m_LoginTimeout.stop();
+ m_Username.clear();
+ m_Password.clear();
+ m_LoginState = LOGIN_NOT_VALID;
+
if (m_LoginReply != nullptr) {
- emit loginFailed(m_LoginReply->errorString());
m_LoginReply->deleteLater();
m_LoginReply = nullptr;
+ emit loginFailed(m_LoginReply->errorString());
} else {
emit loginFailed(tr("Unknown error"));
}
- m_Username.clear();
- m_Password.clear();
}
bool NXMAccessManager::hasLoginCookies() const
{
bool sidCookie = false;
- QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/"));
- foreach (QNetworkCookie cookie, cookies) {
+ QUrl url(ToQString(GameInfo::instance().getNexusPage()) + "/");
+ QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(url);
+ for (const QNetworkCookie &cookie : cookies) {
if (cookie.name() == "sid") {
sidCookie = true;
}
@@ -208,15 +305,35 @@ bool NXMAccessManager::hasLoginCookies() const void NXMAccessManager::loginFinished()
{
m_ProgressDialog.hide();
+
+ m_LoginReply->deleteLater();
+ m_LoginReply = nullptr;
+ m_Username.clear();
+ m_Password.clear();
+
if (hasLoginCookies()) {
+ m_LoginState = LOGIN_VALID;
+ retrieveCredentials();
emit loginSuccessful(true);
} else {
+ m_LoginState = LOGIN_NOT_VALID;
emit loginFailed(tr("Please check your password"));
}
+}
+
- m_LoginTimeout.stop();
+void NXMAccessManager::loginChecked()
+{
+ QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
+ QByteArray data = reply->readAll();
+ m_LoginState = data == "null" ? LOGIN_NOT_VALID
+ : LOGIN_VALID;
+ if (m_LoginState == LOGIN_VALID) {
+ retrieveCredentials();
+ } else {
+ qDebug("cookies seem to be invalid");
+ }
m_LoginReply->deleteLater();
m_LoginReply = nullptr;
- m_Username.clear();
- m_Password.clear();
}
+
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 9d692bba..2b615cdc 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QTimer>
#include <QNetworkReply>
#include <QProgressDialog>
+#include <set>
/**
@@ -43,12 +44,18 @@ public: bool loggedIn() const;
- bool loginAttempted() const { return m_LoginAttempted; }
+ bool loginAttempted() const;
bool loginWaiting() const;
void login(const QString &username, const QString &password);
- void showCookies();
+ void showCookies() const;
+
+ QString userAgent(const QString &subModule = QString()) const;
+
+ void startLoginCheck();
+
+ void refuseLogin();
signals:
@@ -68,11 +75,14 @@ signals: void loginFailed(const QString &message);
- private slots:
+ void credentialsReceived(const QString &userName, bool premium);
+
+private slots:
- void loginFinished();
- void loginError(QNetworkReply::NetworkError errorCode);
- void loginTimeout();
+ void loginChecked();
+ void loginFinished();
+ void loginError(QNetworkReply::NetworkError errorCode);
+ void loginTimeout();
public slots:
@@ -84,10 +94,16 @@ protected: private:
- void pageLogin();
+ void pageLogin();
// void dlLogin();
- bool hasLoginCookies() const;
+ bool hasLoginCookies() const;
+
+ void retrieveCredentials();
+
+private:
+
+ static const std::set<int> s_PremiumAccountStates;
private:
@@ -102,6 +118,14 @@ private: QString m_Password;
bool m_LoginAttempted;
+ enum {
+ LOGIN_NOT_CHECKED,
+ LOGIN_CHECKING,
+ LOGIN_NOT_VALID,
+ LOGIN_ATTEMPT_FAILED,
+ LOGIN_REFUSED,
+ LOGIN_VALID
+ } m_LoginState = LOGIN_NOT_CHECKED;
};
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1a81d33b..50c95f0c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -373,11 +373,13 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *wid m_InstallationManager.setParentWidget(widget);
m_Updater.setUserInterface(widget);
- // this currently wouldn't work reliably if the ui isn't initialized yet to display the result
- if (isOnline() && !m_Settings.offlineMode()) {
- m_Updater.testForUpdate();
- } else {
- qDebug("user doesn't seem to be connected to the internet");
+ if (userInterface != nullptr) {
+ // this currently wouldn't work reliably if the ui isn't initialized yet to display the result
+ if (isOnline() && !m_Settings.offlineMode()) {
+ m_Updater.testForUpdate();
+ } else {
+ qDebug("user doesn't seem to be connected to the internet");
+ }
}
}
@@ -421,21 +423,28 @@ Settings &OrganizerCore::settings() return m_Settings;
}
-bool OrganizerCore::nexusLogin()
+bool OrganizerCore::nexusLogin(bool retry)
{
- QString username, password;
-
NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager();
- if (!accessManager->loginAttempted()
- && !accessManager->loggedIn()
- && (m_Settings.getNexusLogin(username, password)
- || (m_AskForNexusPW
- && queryLogin(username, password)))) {
- accessManager->login(username, password);
- return true;
- } else {
+ if ((accessManager->loginAttempted()
+ || accessManager->loggedIn())
+ && !retry) {
+ // previous attempt, maybe even successful
return false;
+ } else {
+ QString username, password;
+ if ((!retry && m_Settings.getNexusLogin(username, password))
+ || (m_AskForNexusPW && queryLogin(username, password))) {
+ // credentials stored or user entered them manually
+ qDebug("attempt login with username %s", qPrintable(username));
+ accessManager->login(username, password);
+ return true;
+ } else {
+ // no credentials stored and user didn't enter them
+ accessManager->refuseLogin();
+ return false;
+ }
}
}
@@ -1433,9 +1442,15 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message)
{
+ if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == QMessageBox::Yes) {
+ if (nexusLogin(true)) {
+ return;
+ }
+ }
+
if (!m_PendingDownloads.isEmpty()) {
- MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), qApp->activeWindow());
- foreach (QString url, m_PendingDownloads) {
+ MessageDialog::showMessage(tr("login failed: %1. Download will not be associated with an account").arg(message), qApp->activeWindow());
+ for (QString url : m_PendingDownloads) {
downloadRequestedNXM(url);
}
m_PendingDownloads.clear();
diff --git a/src/organizercore.h b/src/organizercore.h index d60ce2ab..014a4ab8 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -182,7 +182,7 @@ public slots: void requestDownload(const QUrl &url, QNetworkReply *reply);
void downloadRequestedNXM(const QString &url);
- bool nexusLogin();
+ bool nexusLogin(bool retry = false);
signals:
|
