158 lines
6.4 KiB
C++
158 lines
6.4 KiB
C++
#include "yourlsclient.h"
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
#include <QUrlQuery>
|
|
#ifndef QT_NO_DEBUG
|
|
#include <QDebug>
|
|
#endif
|
|
|
|
/**
|
|
* @brief Constructs the client.
|
|
* @param parent QObject parent.
|
|
*/
|
|
YourlsClient::YourlsClient (QObject *parent)
|
|
: QObject (parent)
|
|
, m_networkAccessManager (new QNetworkAccessManager (this)) {
|
|
}
|
|
|
|
/**
|
|
* @brief Requests a short URL from the YOURLS API.
|
|
* @param settings API endpoint and signature.
|
|
* @param longUrl Source URL to shorten.
|
|
*/
|
|
void YourlsClient::shortenUrl (const YourlsSettings &settings, const QUrl &longUrl) {
|
|
#ifndef QT_NO_DEBUG
|
|
qDebug() << "[yourls] source URL:" << longUrl << "valid:" << longUrl.isValid()
|
|
<< "scheme:" << longUrl.scheme() << "host:" << longUrl.host();
|
|
#endif
|
|
if (settings.apiUrl.trimmed().isEmpty()) {
|
|
emit shortenFailed (QStringLiteral ("API URL is empty."));
|
|
return;
|
|
}
|
|
|
|
if (settings.signature.trimmed().isEmpty()) {
|
|
emit shortenFailed (QStringLiteral ("API signature is empty."));
|
|
return;
|
|
}
|
|
|
|
if (!longUrl.isValid() || longUrl.scheme().isEmpty() || longUrl.host().isEmpty()) {
|
|
emit shortenFailed (QStringLiteral ("Clipboard does not contain a valid URL."));
|
|
return;
|
|
}
|
|
|
|
QUrl requestUrl (settings.apiUrl);
|
|
if (!requestUrl.isValid()) {
|
|
emit shortenFailed (QStringLiteral ("Configured API URL is invalid: %1").arg (settings.apiUrl));
|
|
return;
|
|
}
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem (QStringLiteral ("signature"), settings.signature);
|
|
query.addQueryItem (QStringLiteral ("action"), QStringLiteral ("shorturl"));
|
|
query.addQueryItem (QStringLiteral ("format"), QStringLiteral ("json"));
|
|
query.addQueryItem (QStringLiteral ("url"),
|
|
QString::fromLatin1 (QUrl::toPercentEncoding (longUrl.toString (QUrl::FullyEncoded))));
|
|
requestUrl.setQuery (query);
|
|
|
|
QNetworkRequest request (requestUrl);
|
|
request.setHeader (QNetworkRequest::UserAgentHeader, QStringLiteral ("yourls-ui/1.0"));
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
// Never log the request query: it contains the API signature.
|
|
qDebug() << "[yourls] sending GET, endpoint:"
|
|
<< requestUrl.adjusted (QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::RemoveUserInfo)
|
|
<< "source URL:" << longUrl;
|
|
#endif
|
|
QNetworkReply *reply = m_networkAccessManager->get (request);
|
|
|
|
connect (reply, &QNetworkReply::finished, this, [this, reply, longUrl]() {
|
|
const QByteArray body = reply->readAll();
|
|
#ifndef QT_NO_DEBUG
|
|
qDebug() << "[yourls] response HTTP status:"
|
|
<< reply->attribute (QNetworkRequest::HttpStatusCodeAttribute)
|
|
<< "network error code:" << reply->error()
|
|
<< "response bytes:" << body.size();
|
|
#endif
|
|
|
|
QJsonParseError parseError;
|
|
const QJsonDocument jsonDocument = QJsonDocument::fromJson (body, &parseError);
|
|
const QJsonObject object = jsonDocument.object();
|
|
const QUrl existingShortUrl (object.value (QStringLiteral ("shorturl")).toString());
|
|
const QUrl existingLongUrl (object.value (QStringLiteral ("url")).toObject()
|
|
.value (QStringLiteral ("url")).toString());
|
|
// YOURLS reports duplicate URLs as HTTP 400, but returns the existing link.
|
|
if (reply->attribute (QNetworkRequest::HttpStatusCodeAttribute).toInt() == 400
|
|
&& object.value (QStringLiteral ("status")).toString() == QStringLiteral ("fail")
|
|
&& object.value (QStringLiteral ("code")).toString() == QStringLiteral ("error:url")
|
|
&& existingLongUrl == longUrl && existingShortUrl.isValid()
|
|
&& (existingShortUrl.scheme() == QStringLiteral ("https")
|
|
|| existingShortUrl.scheme() == QStringLiteral ("http"))
|
|
&& !existingShortUrl.host().isEmpty()) {
|
|
#ifndef QT_NO_DEBUG
|
|
qDebug() << "[yourls] reusing existing short URL:" << existingShortUrl;
|
|
#endif
|
|
reply->deleteLater();
|
|
emit shortenSucceeded (existingShortUrl);
|
|
return;
|
|
}
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
const QString details = QStringLiteral ("Network error: %1\nHTTP status: %2\nResponse body:\n%3")
|
|
.arg (reply->errorString(),
|
|
reply->attribute (QNetworkRequest::HttpStatusCodeAttribute).toString(),
|
|
QString::fromUtf8 (body));
|
|
reply->deleteLater();
|
|
emit shortenFailed (details);
|
|
return;
|
|
}
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
qDebug() << "[yourls] JSON parse error:" << parseError.errorString()
|
|
<< "offset:" << parseError.offset << "is object:" << jsonDocument.isObject();
|
|
#endif
|
|
if (parseError.error != QJsonParseError::NoError || !jsonDocument.isObject()) {
|
|
const QString details = QStringLiteral ("Failed to parse YOURLS response as JSON: %1\nResponse body:\n%2")
|
|
.arg (parseError.errorString(), QString::fromUtf8 (body));
|
|
reply->deleteLater();
|
|
emit shortenFailed (details);
|
|
return;
|
|
}
|
|
|
|
const QString status = object.value (QStringLiteral ("status")).toString();
|
|
const QString shortUrlText = object.value (QStringLiteral ("shorturl")).toString();
|
|
#ifndef QT_NO_DEBUG
|
|
qDebug() << "[yourls] API status:" << status
|
|
<< "code:" << object.value (QStringLiteral ("code")).toVariant()
|
|
<< "short URL:" << shortUrlText;
|
|
#endif
|
|
|
|
if (status.compare (QStringLiteral ("success"), Qt::CaseInsensitive) != 0 || shortUrlText.isEmpty()) {
|
|
QStringList details;
|
|
details << QStringLiteral ("YOURLS returned an error.");
|
|
if (object.contains (QStringLiteral ("message")))
|
|
details << QStringLiteral ("Message: %1").arg (object.value (QStringLiteral ("message")).toString());
|
|
if (object.contains (QStringLiteral ("code")))
|
|
details << QStringLiteral ("Code: %1").arg (object.value (QStringLiteral ("code")).toVariant().toString());
|
|
details << QStringLiteral ("Raw response:\n%1").arg (QString::fromUtf8 (body));
|
|
reply->deleteLater();
|
|
emit shortenFailed (details.join (QStringLiteral ("\n")));
|
|
return;
|
|
}
|
|
|
|
const QUrl shortUrl (shortUrlText);
|
|
if (!shortUrl.isValid() || shortUrl.host().isEmpty()
|
|
|| (shortUrl.scheme() != QStringLiteral ("https") && shortUrl.scheme() != QStringLiteral ("http"))) {
|
|
reply->deleteLater();
|
|
emit shortenFailed (QStringLiteral ("YOURLS returned an invalid short URL: %1").arg (shortUrlText));
|
|
return;
|
|
}
|
|
|
|
reply->deleteLater();
|
|
emit shortenSucceeded (shortUrl);
|
|
});
|
|
}
|