File indexing completed on 2025-02-02 05:02:32

0001 /*
0002     SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "onlineticketretrievaljob.h"
0007 
0008 #include <KItinerary/ExtractorEngine>
0009 #include <KItinerary/ExtractorResult>
0010 #include <KItinerary/HttpResponse>
0011 #include <KItinerary/JsonLdDocument>
0012 
0013 #include <QNetworkAccessManager>
0014 #include <QNetworkReply>
0015 #include <QXmlStreamReader>
0016 
0017 OnlineTicketRetrievalJob::OnlineTicketRetrievalJob(const QString &sourceId, const QVariantMap &arguments, QNetworkAccessManager *nam, QObject *parent)
0018     : QObject(parent)
0019     , m_nam(nam)
0020 {
0021     QNetworkReply *reply = nullptr;
0022 
0023     // TODO this should be done more modular eventually, similar to the extraction side
0024     // e.g. by using some form of request templates, or by service-specific scripts
0025     if (sourceId == QLatin1StringView("db")) {
0026         const auto ref = arguments.value(QLatin1StringView("reference")).toString();
0027         if (ref.size() == 6) {
0028             dbRequestOrderDetails(arguments);
0029         } else {
0030             dbRequestFindOrder(arguments);
0031         }
0032         return;
0033     }
0034     if (sourceId == QLatin1StringView("sncf")) {
0035         // based on https://www.sncf-connect.com/app/trips/search and stripped to the bare minimum that works
0036         QNetworkRequest req(QUrl(QStringLiteral("https://www.sncf-connect.com/bff/api/v1/trips/trips-by-criteria")));
0037         req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/json"));
0038         req.setHeader(QNetworkRequest::UserAgentHeader, QByteArray("Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"));
0039         req.setRawHeader("Accept", "application/json, text/plain, */*");
0040         req.setRawHeader("x-bff-key", "ah1MPO-izehIHD-QZZ9y88n-kku876");
0041         QByteArray postData("{\"reference\":\"" + arguments.value(QLatin1StringView("reference")).toString().toUtf8() + "\",\"name\":\"" + arguments.value(QLatin1StringView("name")).toString().toUtf8() + "\"}");
0042         reply = nam->post(req, postData);
0043         reply->setParent(this);
0044         connect(reply, &QNetworkReply::finished, this, [reply, this]() { handleReply(reply); });
0045         return;
0046     }
0047 
0048     QMetaObject::invokeMethod(this, &OnlineTicketRetrievalJob::finished, Qt::QueuedConnection);
0049 }
0050 
0051 OnlineTicketRetrievalJob::~OnlineTicketRetrievalJob() = default;
0052 
0053 QVector<QVariant> OnlineTicketRetrievalJob::result() const
0054 {
0055     return m_result;
0056 }
0057 
0058 QString OnlineTicketRetrievalJob::errorMessage() const
0059 {
0060     return m_errorMsg;
0061 }
0062 
0063 void OnlineTicketRetrievalJob::dbRequestFindOrder(const QVariantMap &arguments)
0064 {
0065     QNetworkRequest req(QUrl(QStringLiteral("https://fahrkarten.bahn.de/mobile/dbc/xs.go?")));
0066     req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/x-www-form-urlencoded"));
0067     QByteArray postData(R"(<rqfindorder version="1.0"><rqheader v="23080000" os="KCI" app="NAVIGATOR"/><rqorder on=")"
0068         + arguments.value(QLatin1StringView("reference")).toString().toUtf8()
0069         + R"("/><authname tln=")"
0070         + arguments.value(QLatin1StringView("name")).toString().toUtf8()
0071         + R"("/></rqfindorder>)");
0072     auto reply = m_nam->post(req, postData);
0073     reply->setParent(this);
0074     connect(reply, &QNetworkReply::finished, this, [reply, arguments, this]() {
0075         reply->deleteLater();
0076         auto args = arguments;
0077         args.insert(QLatin1StringView("kwid"), dbParseKwid(reply));
0078         dbRequestOrderDetails(args);
0079     });
0080 }
0081 
0082 QString OnlineTicketRetrievalJob::dbParseKwid(QIODevice *io)
0083 {
0084     QXmlStreamReader reader(io);
0085     while (!reader.atEnd()) {
0086         reader.readNextStartElement();
0087         if (const auto kwid = reader.attributes().value(QLatin1StringView("kwid")); !kwid.isEmpty()) {
0088             return kwid.toString();
0089         }
0090     }
0091     return {};
0092 }
0093 
0094 void OnlineTicketRetrievalJob::dbRequestOrderDetails(const QVariantMap &arguments)
0095 {
0096     const auto kwid = arguments.value(QLatin1StringView("kwid")).toString();
0097 
0098     QNetworkRequest req(QUrl(QStringLiteral("https://fahrkarten.bahn.de/mobile/dbc/xs.go?")));
0099     req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/x-www-form-urlencoded"));
0100     QByteArray postData(R"(<rqorderdetails version="1.0"><rqheader v="23040000" os="KCI" app="KCI-Webservice"/><rqorder on=")"
0101         + arguments.value(QLatin1StringView("reference")).toString().toUpper().toUtf8()
0102         + (kwid.isEmpty() ? QByteArray() : QByteArray(R"(" kwid=")" + kwid.toUtf8()))
0103         + R"("/><authname tln=")"
0104         + arguments.value(QLatin1StringView("name")).toString().toUtf8()
0105         + R"("/></rqorderdetails>)");
0106     auto reply = m_nam->post(req, postData);
0107     reply->setParent(this);
0108     connect(reply, &QNetworkReply::finished, this, [reply, this]() { handleReply(reply); });
0109 }
0110 
0111 void OnlineTicketRetrievalJob::handleReply(QNetworkReply *reply)
0112 {
0113     reply->deleteLater();
0114     if (reply->error() != QNetworkReply::NoError) {
0115         m_errorMsg = reply->errorString();
0116     } else {
0117         using namespace KItinerary;
0118         ExtractorEngine engine;
0119         engine.setContent(HttpResponse::fromNetworkReply(reply), u"internal/http-response");
0120         m_result = JsonLdDocument::fromJson(engine.extract());
0121     }
0122     Q_EMIT finished();
0123 }
0124 
0125 #include "moc_onlineticketretrievaljob.cpp"