149 lines
6.6 KiB
C++
149 lines
6.6 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);
|
|
});
|
|
}
|