File indexing completed on 2024-11-24 04:16:55

0001 /*
0002   SPDX-FileCopyrightText: 2022-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006   based on digikam onlinetranslator code
0007 */
0008 
0009 #include "bingengineplugin.h"
0010 #include "translator/plugins/bing/bingtranslator_debug.h"
0011 #include <KLocalizedString>
0012 #include <QCoreApplication>
0013 #include <QJsonArray>
0014 #include <QJsonDocument>
0015 #include <QJsonObject>
0016 #include <QNetworkReply>
0017 #include <QNetworkRequest>
0018 #include <TextTranslator/TranslatorEngineAccessManager>
0019 
0020 QByteArray BingEnginePlugin::sBingKey;
0021 QByteArray BingEnginePlugin::sBingToken;
0022 QString BingEnginePlugin::sBingIg;
0023 QString BingEnginePlugin::sBingIid;
0024 
0025 BingEnginePlugin::BingEnginePlugin(QObject *parent)
0026     : TextTranslator::TranslatorEnginePlugin(parent)
0027 {
0028 }
0029 
0030 BingEnginePlugin::~BingEnginePlugin() = default;
0031 
0032 void BingEnginePlugin::translate()
0033 {
0034     if (sBingKey.isEmpty() || sBingToken.isEmpty()) {
0035         const QUrl url(QStringLiteral("https://www.bing.com/translator"));
0036         QNetworkReply *reply = TextTranslator::TranslatorEngineAccessManager::self()->networkManager()->get(QNetworkRequest(url));
0037         connect(reply, &QNetworkReply::finished, this, [this, reply]() {
0038             parseCredentials(reply);
0039         });
0040         connect(reply, &QNetworkReply::errorOccurred, this, [this, reply](QNetworkReply::NetworkError error) {
0041             slotError(error);
0042             reply->deleteLater();
0043         });
0044     } else {
0045         translateText();
0046     }
0047 }
0048 
0049 void BingEnginePlugin::parseCredentials(QNetworkReply *reply)
0050 {
0051     const QByteArray webSiteData = reply->readAll();
0052     reply->deleteLater();
0053     const QByteArray credentialsBeginString = QByteArrayLiteral("var params_RichTranslateHelper = [");
0054     const int credentialsBeginPos = webSiteData.indexOf(credentialsBeginString);
0055 
0056     if (credentialsBeginPos == -1) {
0057         Q_EMIT translateFailed(i18n("Error: Unable to find Bing credentials in web version."));
0058         return;
0059     }
0060 
0061     const int keyBeginPos = credentialsBeginPos + credentialsBeginString.size();
0062     const int keyEndPos = webSiteData.indexOf(',', keyBeginPos);
0063 
0064     if (keyEndPos == -1) {
0065         Q_EMIT translateFailed(i18n("Error: Unable to extract Bing key from web version."));
0066         return;
0067     }
0068 
0069     sBingKey = webSiteData.mid(keyBeginPos, keyEndPos - keyBeginPos);
0070     const int tokenBeginPos = keyEndPos + 2; // Skip two symbols instead of one because the value is enclosed in quotes
0071     const int tokenEndPos = webSiteData.indexOf('"', tokenBeginPos);
0072 
0073     if (tokenEndPos == -1) {
0074         Q_EMIT translateFailed(i18n("Error: Unable to extract Bing token from web version."));
0075         return;
0076     }
0077 
0078     sBingToken = webSiteData.mid(tokenBeginPos, tokenEndPos - tokenBeginPos);
0079     const int igBeginPos = webSiteData.indexOf("IG");
0080     const int igEndPos = webSiteData.indexOf('"', igBeginPos + 2);
0081 
0082     if (igEndPos == -1) {
0083         Q_EMIT translateFailed(i18n("Error: Unable to extract additional Bing information from web version."));
0084         return;
0085     }
0086 
0087     sBingIg = QString::fromUtf8(webSiteData.mid(igBeginPos, igEndPos - igBeginPos));
0088     const int iidBeginPos = webSiteData.indexOf("data-iid");
0089     const int iidEndPos = webSiteData.indexOf('"', iidBeginPos + 2);
0090 
0091     if (iidEndPos == -1) {
0092         Q_EMIT translateFailed(i18n("Error: Unable to extract additional Bing information from web version."));
0093         return;
0094     }
0095 
0096     sBingIid = QString::fromUtf8(webSiteData.mid(iidBeginPos, iidEndPos - iidBeginPos));
0097 
0098     // qCDebug(TRANSLATOR_BING_LOG) << "sBingIid " << sBingIid << " sBingIg " << sBingIg << " sBingToken " << sBingToken << " sBingKey " << sBingKey;
0099     translateText();
0100 }
0101 
0102 void BingEnginePlugin::translateText()
0103 {
0104     if (verifyFromAndToLanguage()) {
0105         return;
0106     }
0107     clear();
0108 
0109     const QByteArray postData = "&text=" + QUrl::toPercentEncoding(inputText()) + "&fromLang=" + languageCode(from()).toUtf8()
0110         + "&to=" + languageCode(to()).toUtf8() + "&token=" + sBingToken + "&key=" + sBingKey;
0111 
0112     qCDebug(TRANSLATOR_BING_LOG) << " postData " << postData;
0113     QUrl url(QStringLiteral("https://www.bing.com/ttranslatev3"));
0114     url.setQuery(QStringLiteral("IG=%1&IID=%2").arg(sBingIg, sBingIid));
0115     qCDebug(TRANSLATOR_BING_LOG) << " url " << url;
0116 
0117     QNetworkRequest request(url);
0118     request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0119     request.setHeader(QNetworkRequest::UserAgentHeader,
0120                       QStringLiteral("%1/%2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()));
0121 
0122     QNetworkReply *reply = TextTranslator::TranslatorEngineAccessManager::self()->networkManager()->post(request, postData);
0123     connect(reply, &QNetworkReply::errorOccurred, this, [this, reply](QNetworkReply::NetworkError error) {
0124         slotError(error);
0125         reply->deleteLater();
0126     });
0127 
0128     connect(reply, &QNetworkReply::finished, this, [this, reply]() {
0129         reply->deleteLater();
0130         parseTranslation(reply);
0131     });
0132 }
0133 
0134 void BingEnginePlugin::parseTranslation(QNetworkReply *reply)
0135 {
0136     // Parse translation data
0137     const QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll());
0138     qCDebug(TRANSLATOR_BING_LOG) << " jsonResponse " << jsonResponse;
0139     const QJsonObject responseObject = jsonResponse.array().first().toObject();
0140     if (from() == QLatin1String("auto")) {
0141         const QString langCode = responseObject.value(QStringLiteral("detectedLanguage")).toObject().value(QStringLiteral("language")).toString();
0142         setFrom(langCode);
0143         //        if (m_sourceLang == NoLanguage)
0144         //        {
0145         //            resetData(ParsingError, i18n("Error: Unable to parse autodetected language"));
0146         //            return;
0147         //        }
0148     }
0149 
0150     const QJsonObject translationsObject = responseObject.value(QStringLiteral("translations")).toArray().first().toObject();
0151     appendResult(translationsObject.value(QStringLiteral("text")).toString());
0152     if (hasDebug()) {
0153         setJsonDebug(QString::fromUtf8(jsonResponse.toJson(QJsonDocument::Indented)));
0154     }
0155 
0156     qCDebug(TRANSLATOR_BING_LOG) << " mResult " << result();
0157     // m_translationTranslit               += translationsObject.value(QStringLiteral("transliteration")).toObject().value(QStringLiteral("text")).toString();
0158     reply->deleteLater();
0159     Q_EMIT translateDone();
0160 }
0161 
0162 QString BingEnginePlugin::languageCode(const QString &langStr)
0163 {
0164     if (langStr == QLatin1String("auto")) {
0165         return QStringLiteral("auto-detect");
0166     } else if (langStr == QLatin1String("sr")) {
0167         return QStringLiteral("sr-Cyrl");
0168     } else if (langStr == QLatin1String("bs")) {
0169         return QStringLiteral("bs-Latn");
0170     } else if (langStr == QLatin1String("hmn")) {
0171         return QStringLiteral("mww");
0172     } else if (langStr == QLatin1String("zh")) {
0173         return QStringLiteral("zh-Hans");
0174     } else if (langStr == QLatin1String("zt")) {
0175         return QStringLiteral("zh-Hant");
0176     }
0177     return langStr;
0178 }
0179 
0180 #include "moc_bingengineplugin.cpp"