Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding osu! API #86

Merged
merged 18 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ endif()
target_include_directories(miraya PRIVATE
${FORMS_DIR}
${SOURCE_DIR}
${SOURCE_TWITCH_DIR}
${SOURCE_OSU_DIR}
${SOURCE_UTILS_DIR}
${SOURCE_SETUPWIZARD_DIR}
)
# =========================== linking libraries ===========================
target_link_libraries(miraya PRIVATE Qt6::Core Qt6::Widgets Qt6::WebSockets Qt6::Network)
71 changes: 69 additions & 2 deletions forms/preferences.ui
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
<normaloff>:/resources/images/logo_gosumemory.ico</normaloff>:/resources/images/logo_gosumemory.ico</iconset>
</property>
</item>
<item>
<property name="text">
<string>osu! API</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/resources/images/logo_osu.png</normaloff>:/resources/images/logo_osu.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Twitch</string>
Expand All @@ -61,7 +70,7 @@
</item>
<item>
<property name="text">
<string>Osu! IRC</string>
<string>osu! IRC</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
Expand Down Expand Up @@ -161,6 +170,64 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="osuapiPage">
<layout class="QGridLayout" name="gridLayout_7">
<item row="2" column="1">
<widget class="QLineEdit" name="osuapiClientSecretLineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="osuapiClientIdLineEdit">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="osuapiClientIdLabel">
<property name="text">
<string>Client ID</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="osuapiClientSecretLabel">
<property name="text">
<string>Client Secret</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_11">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<spacer name="verticalSpacer_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="twitchPage">
<layout class="QGridLayout" name="gridLayout_4">
<item row="3" column="1">
Expand Down Expand Up @@ -325,7 +392,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page">
<widget class="QWidget" name="backupPage">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="backupBtn">
Expand Down
58 changes: 58 additions & 0 deletions forms/setupwizard.ui
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,64 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="osuapi">
<property name="title">
<string>osu! API</string>
</property>
<property name="subTitle">
<string>This will help miraya to grab data from beatmaps when they are requested.</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0" rowspan="2">
<layout class="QFormLayout" name="osuapiFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="osuapiClientIdLabel">
<property name="text">
<string>osu! Client ID</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="osuapiClientIdLineEdit">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly</set>
</property>
<property name="maxLength">
<number>6</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="osuapiClientSecretLabel">
<property name="text">
<string>osu! Client Secret</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="osuapiClientSecretLineEdit">
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="osuapiExplanationLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Go to your &lt;a href=&quot;https://osu.ppy.sh/home/account/edit&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#4285f4;&quot;&gt;osu! account settings&lt;/span&gt;&lt;/a&gt; and create a new OAuth application (at the bottom of the page). &lt;/p&gt;&lt;p&gt;You can chose whatever name you want, and there is no need to put any application callback URL.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="twitch">
<property name="title">
<string>Twitch Setup</string>
Expand Down
9 changes: 4 additions & 5 deletions src/backup/backup.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
#include <QObject>
#include <QSettings>

class Backup : public QObject
class Backup
{
Q_OBJECT
public:
static void backup(QString filePath, bool includeSensitiveInfo);
static void restore(QString filePath);
public:
static void backup(QString filePath, bool includeSensitiveInfo);
static void restore(QString filePath);
};

#endif // BACKUP_H
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ int main(int argc, char **argv) {
app.setOrganizationName("miraya");
app.setOrganizationDomain("github.com/MirayaProject");
app.setApplicationName("bot");
app.setApplicationVersion("1.2.0");
app.setApplicationVersion("1.3.0-alpha.1");
#ifdef Q_OS_LINUX
app.setWindowIcon(QIcon(":/resources/logo/logo.png"));
#endif
Expand Down
17 changes: 14 additions & 3 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ MainWindow::MainWindow(QWidget *parent) :
updater = new Updater(this);
updater->checkVersion();

osuApi = new OsuApi();

setupSignals();
if(!settings.value("setup/completed").toBool()){
on_actionStart_Setup_triggered();
Expand Down Expand Up @@ -143,9 +145,18 @@ void MainWindow::onGosumemoryClientMessageReceived(GosuMemoryDataWrapper message
void MainWindow::onTwitchClientMessageReceived(TwitchDataWrapper message)
{
// TODO: this should not be here
for (auto val : Utils().getOsuBeatmapUrls(message.getMessage())) {
qDebug() << "[MainWindow] Osu beatmap url: " << val;
osuIrcClient->sendMap(QUrl(val), message);
for (QString url : Utils::getOsuBeatmapUrls(message.getMessage())) {
qDebug() << "[MainWindow] Osu beatmap url: " << url;

int beatmapId = Utils::getBeatmapIdFromOsuBeatmapLink(url);
if (beatmapId > 0 && osuApi->isValid()) {
// TODO: also this should not be here
QJsonObject mapData = osuApi->getBeatmapInfo(beatmapId);
osuIrcClient->sendMap(mapData, message);
}
else {
osuIrcClient->sendMap(QUrl(url), message);
}
}

QLabel* label = getTwitchChatMessage(message.getUsername(), message.getMessage());
Expand Down
3 changes: 2 additions & 1 deletion src/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "twitchdatawrapper.h"
#include "updater.h"
#include "utils.h"
#include "osu/api/osuapi.h"


namespace Ui {
Expand All @@ -41,7 +42,6 @@ class MainWindow : public QMainWindow
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();


private slots:
void on_btnStart_clicked();
void on_actionStart_Setup_triggered();
Expand Down Expand Up @@ -78,6 +78,7 @@ class MainWindow : public QMainWindow
GosumemoryClient *gosumemoryClient;
OsuIrcClient *osuIrcClient;
Updater *updater;
OsuApi *osuApi;

QLabel *ircConnectionLabel;
QLabel *twitchConnectionLabel;
Expand Down
42 changes: 42 additions & 0 deletions src/osu/api/auth/clientCredentialsFlow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "clientCredentialsFlow.h"

QJsonObject ClientCredentialsFlow::getToken(QString clientId, QString clientSecret, QString oAuthUrl)
{
qDebug() << "[ClientCredentialsFlow] getToken";
QNetworkAccessManager manager;
QNetworkRequest request((QUrl(oAuthUrl)));

request.setHeader(
QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded"
);
request.setHeader(
QNetworkRequest::UserAgentHeader,
"Miraya"
);

QUrlQuery params;
params.addQueryItem("client_id", clientId);
params.addQueryItem("client_secret", clientSecret);
params.addQueryItem("grant_type", "client_credentials");
params.addQueryItem("scope", "public");

QNetworkReply *reply = manager.post(request, params.toString(QUrl::FullyEncoded).toUtf8());

QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();

int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

qDebug() << "[ClientCredentialsFlow] HTTP Status code: " << httpStatusCode;
if (httpStatusCode == 200) {
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
QJsonObject obj = doc.object();
return obj;
}
qDebug() << "[ClientCredentialsFlow] Failed to get token";
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qDebug() << QString(doc.toJson(QJsonDocument::Compact));
return doc.object();
}
24 changes: 24 additions & 0 deletions src/osu/api/auth/clientCredentialsFlow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef CLIENTCREDENTIALSFLOW_H
#define CLIENTCREDENTIALSFLOW_H

#include <QUrl>
#include <QDebug>
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>

class ClientCredentialsFlow
{
public:
static QJsonObject getToken(
QString clientId,
QString clientSecret,
QString oAuthUrl = QString("https://osu.ppy.sh/oauth/token")
);
};

#endif // CLIENTCREDENTIALSFLOW_H
58 changes: 58 additions & 0 deletions src/osu/api/osuapi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "osuapi.h"

OsuApi::OsuApi()
{
qDebug() << "[OsuApi] Init";
QSettings settings;
clientId = settings.value("osuapi/clientId").toString();
clientSecret = settings.value("osuapi/clientSecret").toString();
oAuthUrl = "https://osu.ppy.sh/oauth/token";

if (!(clientId.isEmpty() || clientSecret.isEmpty())) {
token = ClientCredentialsFlow::getToken(clientId, clientSecret, oAuthUrl);
}
else {
qDebug() << "[OsuApi] Client ID or Client Secret are empty";
}
}

QJsonObject OsuApi::getBeatmapInfo(int beatmapId)
{
// TODO: A lot can be split into smaller functions
// TODO: also, the QEventLoop is making everything synchronous, which is suboptimal to say the least.
qDebug() << "[OsuApi] getBeatmapInfo";
QNetworkAccessManager manager;
QUrl url(QString("https://osu.ppy.sh/api/v2/beatmaps/%1").arg(beatmapId));

QNetworkRequest request(url);

QString accessToken = token["access_token"].toString();

request.setRawHeader(
QByteArray("Authorization"),
(QString("Bearer %1").arg(accessToken)).toUtf8()
);

QNetworkReply *reply = manager.get(request);

QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();

qDebug() << "[OsuApi] get request done";
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << "[OsuApi] HTTP Status code: " << httpStatusCode;
if (httpStatusCode == 200) {
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
QJsonObject obj = doc.object();
qDebug() << "[OsuApi] Beatmap info: " << obj;
return obj;
}
qDebug() << "[OsuApi] Error: " << reply->errorString();
return QJsonObject();
}

bool OsuApi::isValid()
{
return !(clientId.isEmpty() || clientSecret.isEmpty());
}
Loading