File indexing completed on 2025-04-27 03:58:38
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2018-02-22 0007 * Description : A text translator using web-services - Bing methods. 0008 * 0009 * SPDX-FileCopyrightText: 2018-2022 by Hennadii Chernyshchyk <genaloner at gmail dot com> 0010 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "donlinetranslator_p.h" 0017 0018 namespace Digikam 0019 { 0020 0021 void DOnlineTranslator::slotRequestBingCredentials() 0022 { 0023 const QUrl url(QStringLiteral("https://www.bing.com/translator")); 0024 d->currentReply = d->networkManager->get(QNetworkRequest(url)); 0025 } 0026 0027 void DOnlineTranslator::slotParseBingCredentials() 0028 { 0029 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0030 { 0031 return; 0032 } 0033 0034 d->currentReply->deleteLater(); 0035 0036 if (d->currentReply->error() != QNetworkReply::NoError) 0037 { 0038 resetData(NetworkError, d->currentReply->errorString()); 0039 return; 0040 } 0041 0042 const QByteArray webSiteData = d->currentReply->readAll(); 0043 const QByteArray credentialsBeginString = "var params_RichTranslateHelper = ["; 0044 const int credentialsBeginPos = webSiteData.indexOf(credentialsBeginString); 0045 0046 if (credentialsBeginPos == -1) 0047 { 0048 resetData(ParsingError, i18n("Error: Unable to find Bing credentials in web version.")); 0049 return; 0050 } 0051 0052 const int keyBeginPos = credentialsBeginPos + credentialsBeginString.size(); 0053 const int keyEndPos = webSiteData.indexOf(',', keyBeginPos); 0054 0055 if (keyEndPos == -1) 0056 { 0057 resetData(ParsingError, i18n("Error: Unable to extract Bing key from web version.")); 0058 return; 0059 } 0060 0061 Private::s_bingKey = webSiteData.mid(keyBeginPos, keyEndPos - keyBeginPos); 0062 const int tokenBeginPos = keyEndPos + 2; // Skip two symbols instead of one because the value is enclosed in quotes 0063 const int tokenEndPos = webSiteData.indexOf('"', tokenBeginPos); 0064 0065 if (tokenEndPos == -1) 0066 { 0067 resetData(ParsingError, i18n("Error: Unable to extract Bing token from web version.")); 0068 return; 0069 } 0070 0071 Private::s_bingToken = webSiteData.mid(tokenBeginPos, tokenEndPos - tokenBeginPos); 0072 const int igBeginPos = webSiteData.indexOf("IG"); 0073 const int igEndPos = webSiteData.indexOf('"', igBeginPos + 2); 0074 0075 if (igEndPos == -1) 0076 { 0077 resetData(ParsingError, i18n("Error: Unable to extract additional Bing information from web version.")); 0078 return; 0079 } 0080 0081 Private::s_bingIg = QString::fromUtf8(webSiteData.mid(igBeginPos, igEndPos - igBeginPos)); 0082 const int iidBeginPos = webSiteData.indexOf("data-iid"); 0083 const int iidEndPos = webSiteData.indexOf('"', iidBeginPos + 2); 0084 0085 if (iidEndPos == -1) 0086 { 0087 resetData(ParsingError, i18n("Error: Unable to extract additional Bing information from web version.")); 0088 return; 0089 } 0090 0091 Private::s_bingIid = QString::fromUtf8(webSiteData.mid(iidBeginPos, iidEndPos - iidBeginPos)); 0092 } 0093 0094 void DOnlineTranslator::slotRequestBingTranslate() 0095 { 0096 const QString sourceText = sender()->property(Private::s_textProperty).toString(); 0097 0098 // Generate POST data 0099 0100 const QByteArray postData = "&text=" + QUrl::toPercentEncoding(sourceText) 0101 + "&fromLang=" + languageApiCode(Bing, d->sourceLang).toUtf8() 0102 + "&to=" + languageApiCode(Bing, d->translationLang).toUtf8() 0103 + "&token=" + Private::s_bingToken 0104 + "&key=" + Private::s_bingKey; 0105 0106 QUrl url(QStringLiteral("https://www.bing.com/ttranslatev3")); 0107 url.setQuery(QStringLiteral("IG=%1&IID=%2").arg(Private::s_bingIg, Private::s_bingIid)); 0108 0109 // Setup request 0110 0111 QNetworkRequest request; 0112 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); 0113 request.setHeader(QNetworkRequest::UserAgentHeader, QString::fromUtf8("%1/%2").arg(QCoreApplication::applicationName()).arg(QCoreApplication::applicationVersion())); 0114 request.setUrl(url); 0115 0116 // Make reply 0117 0118 d->currentReply = d->networkManager->post(request, postData); 0119 } 0120 0121 void DOnlineTranslator::slotParseBingTranslate() 0122 { 0123 d->currentReply->deleteLater(); 0124 0125 // Check for errors 0126 0127 if (d->currentReply->error() != QNetworkReply::NoError) 0128 { 0129 resetData(NetworkError, d->currentReply->errorString()); 0130 return; 0131 } 0132 0133 // Parse translation data 0134 0135 const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll()); 0136 const QJsonObject responseObject = jsonResponse.array().first().toObject(); 0137 0138 if (d->sourceLang == Auto) 0139 { 0140 const QString langCode = responseObject.value(QStringLiteral("detectedLanguage")).toObject().value(QStringLiteral("language")).toString(); 0141 d->sourceLang = language(Bing, langCode); 0142 0143 if (d->sourceLang == NoLanguage) 0144 { 0145 resetData(ParsingError, i18n("Error: Unable to parse autodetected language")); 0146 return; 0147 } 0148 0149 if (d->onlyDetectLanguage) 0150 return; 0151 } 0152 0153 const QJsonObject translationsObject = responseObject.value(QStringLiteral("translations")).toArray().first().toObject(); 0154 d->translation += translationsObject.value(QStringLiteral("text")).toString(); 0155 d->translationTranslit += translationsObject.value(QStringLiteral("transliteration")).toObject().value(QStringLiteral("text")).toString(); 0156 } 0157 0158 void DOnlineTranslator::slotRequestBingDictionary() 0159 { 0160 // Check if language is supported (need to check here because language may be autodetected) 0161 0162 if (!isSupportDictionary(Bing, d->sourceLang, d->translationLang) && !d->source.contains(QLatin1Char(' '))) 0163 { 0164 auto* state = qobject_cast<QState *>(sender()); 0165 state->addTransition(new QFinalState(state->parentState())); 0166 return; 0167 } 0168 0169 // Generate POST data 0170 0171 const QByteArray postData = "&text=" + QUrl::toPercentEncoding(sender()->property(Private::s_textProperty).toString()) 0172 + "&from=" + languageApiCode(Bing, d->sourceLang).toUtf8() 0173 + "&to=" + languageApiCode(Bing, d->translationLang).toUtf8(); 0174 0175 QNetworkRequest request; 0176 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); 0177 request.setUrl(QUrl(QStringLiteral("https://www.bing.com/tlookupv3"))); 0178 0179 d->currentReply = d->networkManager->post(request, postData); 0180 } 0181 0182 void DOnlineTranslator::slotParseBingDictionary() 0183 { 0184 if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>()) 0185 { 0186 return; 0187 } 0188 0189 d->currentReply->deleteLater(); 0190 0191 // Check for errors 0192 0193 if (d->currentReply->error() != QNetworkReply::NoError) 0194 { 0195 resetData(NetworkError, d->currentReply->errorString()); 0196 return; 0197 } 0198 0199 const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll()); 0200 const QJsonObject responseObject = jsonResponse.array().first().toObject(); 0201 0202 for (const QJsonValueRef dictionaryData : responseObject.value(QStringLiteral("translations")).toArray()) 0203 { 0204 const QJsonObject dictionaryObject = dictionaryData.toObject(); 0205 const QString typeOfSpeech = dictionaryObject.value(QStringLiteral("posTag")).toString().toLower(); 0206 const QString word = dictionaryObject.value(QStringLiteral("displayTarget")).toString().toLower(); 0207 const QJsonArray translationsArray = dictionaryObject.value(QStringLiteral("backTranslations")).toArray(); 0208 QStringList translations; 0209 translations.reserve(translationsArray.size()); 0210 0211 for (const QJsonValue &wordTranslation : translationsArray) 0212 { 0213 translations.append(wordTranslation.toObject().value(QStringLiteral("displayText")).toString()); 0214 } 0215 0216 d->translationOptions[typeOfSpeech].append({word, {}, translations}); 0217 } 0218 } 0219 0220 void DOnlineTranslator::buildBingStateMachine() 0221 { 0222 // States 0223 0224 auto* credentialsState = new QState(d->stateMachine); // Generate credentials from web version first to access API 0225 auto* translationState = new QState(d->stateMachine); 0226 auto* dictionaryState = new QState(d->stateMachine); 0227 auto* finalState = new QFinalState(d->stateMachine); 0228 d->stateMachine->setInitialState(credentialsState); 0229 0230 // Transitions 0231 0232 credentialsState->addTransition(credentialsState, &QState::finished, translationState); 0233 translationState->addTransition(translationState, &QState::finished, dictionaryState); 0234 dictionaryState->addTransition(dictionaryState, &QState::finished, finalState); 0235 0236 // Setup credentials state 0237 0238 if (Private::s_bingKey.isEmpty() || Private::s_bingToken.isEmpty()) 0239 { 0240 buildNetworkRequestState(credentialsState, 0241 &DOnlineTranslator::slotRequestBingCredentials, 0242 &DOnlineTranslator::slotParseBingCredentials); 0243 } 0244 else 0245 { 0246 credentialsState->setInitialState(new QFinalState(credentialsState)); 0247 } 0248 0249 // Setup translation state 0250 0251 buildSplitNetworkRequest(translationState, 0252 &DOnlineTranslator::slotRequestBingTranslate, 0253 &DOnlineTranslator::slotParseBingTranslate, 0254 d->source, 0255 Private::s_bingTranslateLimit); 0256 0257 // Setup dictionary state 0258 0259 if (d->translationOptionsEnabled && !isContainsSpace(d->source)) 0260 { 0261 buildNetworkRequestState(dictionaryState, 0262 &DOnlineTranslator::slotRequestBingDictionary, 0263 &DOnlineTranslator::slotParseBingDictionary, 0264 d->source); 0265 } 0266 else 0267 { 0268 dictionaryState->setInitialState(new QFinalState(dictionaryState)); 0269 } 0270 } 0271 0272 void DOnlineTranslator::buildBingDetectStateMachine() 0273 { 0274 // States 0275 0276 auto* detectState = new QState(d->stateMachine); 0277 auto* finalState = new QFinalState(d->stateMachine); 0278 d->stateMachine->setInitialState(detectState); 0279 0280 detectState->addTransition(detectState, &QState::finished, finalState); 0281 0282 // Setup translation state 0283 0284 const QString text = d->source.left(getSplitIndex(d->source, Private::s_bingTranslateLimit)); 0285 0286 buildNetworkRequestState(detectState, 0287 &DOnlineTranslator::slotRequestBingTranslate, 0288 &DOnlineTranslator::slotParseBingTranslate, 0289 text); 0290 } 0291 0292 } // namespace Digikam