File indexing completed on 2025-04-27 03:58:39

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 - Tnadex 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::slotRequestYandexKey()
0022 {
0023     const QUrl url(QStringLiteral("https://translate.yandex.com"));
0024     d->currentReply = d->networkManager->get(QNetworkRequest(url));
0025 }
0026 
0027 void DOnlineTranslator::slotParseYandexKey()
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     // Check availability of service
0043 
0044     const QByteArray webSiteData = d->currentReply->readAll();
0045 
0046     if (webSiteData.isEmpty()                        ||
0047         webSiteData.contains("<title>Oops!</title>") ||
0048         webSiteData.contains("<title>302 Found</title>"))
0049     {
0050         resetData(ServiceError, i18n("Error: Engine systems have detected suspicious traffic "
0051                                      "from your computer network. Please try your request again later."));
0052         return;
0053     }
0054 
0055     const QByteArray sidBeginString = "SID: '";
0056     const int sidBeginStringPos     = webSiteData.indexOf(sidBeginString);
0057 
0058     if (sidBeginStringPos == -1)
0059     {
0060         resetData(ParsingError, i18n("Error: Unable to find Yandex SID in web version."));
0061         return;
0062     }
0063 
0064     const int sidBeginPosition = sidBeginStringPos + sidBeginString.size();
0065     const int sidEndPosition   = webSiteData.indexOf('\'', sidBeginPosition);
0066 
0067     if (sidEndPosition == -1)
0068     {
0069         resetData(ParsingError, i18n("Error: Unable to extract Yandex SID from web version."));
0070         return;
0071     }
0072 
0073     // Yandex show reversed parts of session ID, need to decode
0074 
0075     const QString sid    = QString::fromUtf8(webSiteData.mid(sidBeginPosition,
0076                                                              sidEndPosition - sidBeginPosition));
0077 
0078     QStringList sidParts = sid.split(QLatin1Char('.'));
0079 
0080     for (int i = 0 ; i < sidParts.size() ; ++i)
0081     {
0082         std::reverse(sidParts[i].begin(), sidParts[i].end());
0083     }
0084 
0085     Private::s_yandexKey = sidParts.join(QLatin1Char('.'));
0086 }
0087 
0088 void DOnlineTranslator::slotRequestYandexTranslate()
0089 {
0090     const QString sourceText = sender()->property(Private::s_textProperty).toString();
0091 
0092     QString lang;
0093 
0094     if (d->sourceLang == Auto)
0095     {
0096         lang = languageApiCode(Yandex, d->translationLang);
0097     }
0098     else
0099     {
0100         lang = languageApiCode(Yandex, d->sourceLang) + QLatin1Char('-') + languageApiCode(Yandex, d->translationLang);
0101     }
0102 
0103     // Generate API url
0104 
0105     QUrl url(QStringLiteral("https://translate.yandex.net/api/v1/tr.json/translate"));
0106 
0107     url.setQuery(QStringLiteral("id=%1-2-0&srv=tr-text&text=%2&lang=%3")
0108                      .arg(Private::s_yandexKey,
0109                           QString::fromUtf8(QUrl::toPercentEncoding(sourceText)),
0110                           lang));
0111 
0112     // Setup request
0113 
0114     QNetworkRequest request;
0115     request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0116     request.setUrl(url);
0117 
0118     // Make reply
0119 
0120     d->currentReply = d->networkManager->post(request, QByteArray());
0121 }
0122 
0123 void DOnlineTranslator::slotParseYandexTranslate()
0124 {
0125     if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>())
0126     {
0127         return;
0128     }
0129 
0130     d->currentReply->deleteLater();
0131 
0132     // Check for errors
0133 
0134     if (d->currentReply->error() != QNetworkReply::NoError)
0135     {
0136         // Network errors
0137 
0138         if (d->currentReply->error() < QNetworkReply::ContentAccessDenied)
0139         {
0140             resetData(NetworkError, d->currentReply->errorString());
0141             return;
0142         }
0143 
0144         // Parse data to get request error type
0145 
0146         Private::s_yandexKey.clear();
0147         const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll());
0148         resetData(ServiceError, jsonResponse.object().value(QStringLiteral("message")).toString());
0149 
0150         return;
0151     }
0152 
0153     // Read Json
0154 
0155     const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll());
0156     const QJsonObject jsonData       = jsonResponse.object();
0157 
0158     // Parse language
0159 
0160     if (d->sourceLang == Auto)
0161     {
0162         QString sourceCode = jsonData.value(QStringLiteral("lang")).toString();
0163         sourceCode         = sourceCode.left(sourceCode.indexOf(QLatin1Char('-')));
0164         d->sourceLang       = language(Yandex, sourceCode);
0165 
0166         if (d->sourceLang == NoLanguage)
0167         {
0168             resetData(ParsingError, i18n("Error: Unable to parse autodetected language"));
0169             return;
0170         }
0171 
0172         if (d->onlyDetectLanguage)
0173         {
0174             return;
0175         }
0176     }
0177 
0178     // Parse translation data
0179 
0180     d->translation += jsonData.value(QStringLiteral("text")).toArray().at(0).toString();
0181 }
0182 
0183 void DOnlineTranslator::slotRequestYandexSourceTranslit()
0184 {
0185     requestYandexTranslit(d->sourceLang);
0186 }
0187 
0188 void DOnlineTranslator::slotParseYandexSourceTranslit()
0189 {
0190     parseYandexTranslit(d->sourceTranslit);
0191 }
0192 
0193 void DOnlineTranslator::slotRequestYandexTranslationTranslit()
0194 {
0195     requestYandexTranslit(d->translationLang);
0196 }
0197 
0198 void DOnlineTranslator::slotParseYandexTranslationTranslit()
0199 {
0200     parseYandexTranslit(d->translationTranslit);
0201 }
0202 
0203 void DOnlineTranslator::slotRequestYandexDictionary()
0204 {
0205     // Check if language is supported (need to check here because language may be autodetected)
0206 
0207     if (!isSupportDictionary(Yandex, d->sourceLang, d->translationLang) &&
0208         !d->source.contains(QLatin1Char(' ')))
0209     {
0210         auto* state = qobject_cast<QState*>(sender());
0211         state->addTransition(new QFinalState(state->parentState()));
0212         return;
0213     }
0214 
0215     // Generate API url
0216 
0217     const QString text = sender()->property(Private::s_textProperty).toString();
0218     QUrl url(QStringLiteral("https://dictionary.yandex.net/dicservice.json/lookupMultiple"));
0219 
0220     url.setQuery(QStringLiteral("text=%1&ui=%2&dict=%3-%4")
0221                      .arg(QString::fromUtf8(QUrl::toPercentEncoding(text)),
0222                           languageApiCode(Yandex, d->uiLang),
0223                           languageApiCode(Yandex, d->sourceLang),
0224                           languageApiCode(Yandex, d->translationLang)));
0225 
0226     d->currentReply = d->networkManager->get(QNetworkRequest(url));
0227 }
0228 
0229 void DOnlineTranslator::slotParseYandexDictionary()
0230 {
0231     if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>())
0232     {
0233         return;
0234     }
0235 
0236     d->currentReply->deleteLater();
0237 
0238     if (d->currentReply->error() != QNetworkReply::NoError)
0239     {
0240         resetData(NetworkError, d->currentReply->errorString());
0241         return;
0242     }
0243 
0244     // Parse reply
0245 
0246     const QJsonDocument jsonResponse = QJsonDocument::fromJson(d->currentReply->readAll());
0247     const QJsonValue jsonData        = jsonResponse.object().value(languageApiCode(Yandex, d->sourceLang) +
0248                                        QLatin1Char('-')                                                  +
0249                                        languageApiCode(Yandex, d->translationLang)).toObject().value(QStringLiteral("regular"));
0250 
0251     if (d->sourceTranscriptionEnabled)
0252     {
0253         d->sourceTranscription = jsonData.toArray().at(0).toObject().value(QStringLiteral("ts")).toString();
0254     }
0255 
0256     for (const QJsonValueRef typeOfSpeechData : jsonData.toArray())
0257     {
0258         QJsonObject typeOfSpeechObject = typeOfSpeechData.toObject();
0259         const QString typeOfSpeech     = typeOfSpeechObject.value(QStringLiteral("pos")).toObject().value(QStringLiteral("text")).toString();
0260 
0261         for (const QJsonValueRef wordData : typeOfSpeechObject.value(QStringLiteral("tr")).toArray())
0262         {
0263             // Parse translation options
0264 
0265             const QJsonObject wordObject       = wordData.toObject();
0266             const QString word                 = wordObject.value(QStringLiteral("text")).toString();
0267             const QString gender               = wordObject.value(QStringLiteral("gen")).toObject().value(QStringLiteral("text")).toString();
0268             const QJsonArray translationsArray = wordObject.value(QStringLiteral("mean")).toArray();
0269             QStringList translations;
0270             translations.reserve(translationsArray.size());
0271 
0272             for (const QJsonValue &wordTranslation : translationsArray)
0273             {
0274                 translations.append(wordTranslation.toObject().value(QStringLiteral("text")).toString());
0275             }
0276 
0277             d->translationOptions[typeOfSpeech].append({word, gender, translations});
0278         }
0279     }
0280 }
0281 
0282 void DOnlineTranslator::buildYandexStateMachine()
0283 {
0284     // States
0285 
0286     auto* keyState                  = new QState(d->stateMachine); // Generate SID from web version first to access API
0287     auto* translationState          = new QState(d->stateMachine);
0288     auto* sourceTranslitState       = new QState(d->stateMachine);
0289     auto* translationTranslitState  = new QState(d->stateMachine);
0290     auto* dictionaryState           = new QState(d->stateMachine);
0291     auto* finalState                = new QFinalState(d->stateMachine);
0292     d->stateMachine->setInitialState(keyState);
0293 
0294     // Transitions
0295 
0296     keyState->addTransition(keyState, &QState::finished, translationState);
0297     translationState->addTransition(translationState, &QState::finished, sourceTranslitState);
0298     sourceTranslitState->addTransition(sourceTranslitState, &QState::finished, translationTranslitState);
0299     translationTranslitState->addTransition(translationTranslitState, &QState::finished, dictionaryState);
0300     dictionaryState->addTransition(dictionaryState, &QState::finished, finalState);
0301 
0302     // Setup key state
0303 
0304     if (Private::s_yandexKey.isEmpty())
0305     {
0306         buildNetworkRequestState(keyState,
0307                                  &DOnlineTranslator::slotRequestYandexKey,
0308                                  &DOnlineTranslator::slotParseYandexKey);
0309     }
0310     else
0311     {
0312         keyState->setInitialState(new QFinalState(keyState));
0313     }
0314 
0315     // Setup translation state
0316 
0317     buildSplitNetworkRequest(translationState,
0318                              &DOnlineTranslator::slotRequestYandexTranslate,
0319                              &DOnlineTranslator::slotParseYandexTranslate,
0320                              d->source,
0321                              Private::s_yandexTranslateLimit);
0322 
0323     // Setup source translit state
0324 
0325     if (d->sourceTranslitEnabled)
0326     {
0327         buildSplitNetworkRequest(sourceTranslitState,
0328                                  &DOnlineTranslator::slotRequestYandexSourceTranslit,
0329                                  &DOnlineTranslator::slotParseYandexSourceTranslit,
0330                                  d->source,
0331                                  Private::s_yandexTranslitLimit);
0332     }
0333     else
0334     {
0335         sourceTranslitState->setInitialState(new QFinalState(sourceTranslitState));
0336     }
0337 
0338     // Setup translation translit state
0339 
0340     if (d->translationTranslitEnabled)
0341     {
0342         buildSplitNetworkRequest(translationTranslitState,
0343                                  &DOnlineTranslator::slotRequestYandexTranslationTranslit,
0344                                  &DOnlineTranslator::slotParseYandexTranslationTranslit,
0345                                  d->translation,
0346                                  Private::s_yandexTranslitLimit);
0347     }
0348     else
0349     {
0350         translationTranslitState->setInitialState(new QFinalState(translationTranslitState));
0351     }
0352 
0353     // Setup dictionary state
0354 
0355     if (d->translationOptionsEnabled && !isContainsSpace(d->source))
0356     {
0357         buildNetworkRequestState(dictionaryState,
0358                                  &DOnlineTranslator::slotRequestYandexDictionary,
0359                                  &DOnlineTranslator::slotParseYandexDictionary,
0360                                  d->source);
0361     }
0362     else
0363     {
0364         dictionaryState->setInitialState(new QFinalState(dictionaryState));
0365     }
0366 }
0367 
0368 void DOnlineTranslator::buildYandexDetectStateMachine()
0369 {
0370     // States
0371 
0372     auto* keyState    = new QState(d->stateMachine); // Generate SID from web version first to access API
0373     auto* detectState = new QState(d->stateMachine);
0374     auto* finalState  = new QFinalState(d->stateMachine);
0375     d->stateMachine->setInitialState(keyState);
0376 
0377     // Transitions
0378 
0379     keyState->addTransition(keyState, &QState::finished, detectState);
0380     detectState->addTransition(detectState, &QState::finished, finalState);
0381 
0382     // Setup key state
0383 
0384     if (Private::s_yandexKey.isEmpty())
0385     {
0386         buildNetworkRequestState(keyState,
0387                                  &DOnlineTranslator::slotRequestYandexKey,
0388                                  &DOnlineTranslator::slotParseYandexKey);
0389     }
0390     else
0391     {
0392         keyState->setInitialState(new QFinalState(keyState));
0393     }
0394 
0395     // Setup detect state
0396 
0397     const QString text = d->source.left(getSplitIndex(d->source, Private::s_yandexTranslateLimit));
0398 
0399     buildNetworkRequestState(detectState,
0400                              &DOnlineTranslator::slotRequestYandexTranslate,
0401                              &DOnlineTranslator::slotParseYandexTranslate,
0402                              text);
0403 }
0404 
0405 void DOnlineTranslator::requestYandexTranslit(Language language)
0406 {
0407     // Check if language is supported (need to check here because language may be autodetected)
0408 
0409     if (!isSupportTranslit(Yandex, language))
0410     {
0411         auto* state = qobject_cast<QState *>(sender());
0412         state->addTransition(new QFinalState(state->parentState()));
0413 
0414         return;
0415     }
0416 
0417     const QString text = sender()->property(Private::s_textProperty).toString();
0418 
0419     // Generate API url
0420 
0421     QUrl url(QStringLiteral("https://translate.yandex.net/translit/translit"));
0422     url.setQuery(QString::fromUtf8("text=%1&lang=%2")
0423                  .arg(QString::fromUtf8(QUrl::toPercentEncoding(text)))
0424                  .arg(languageApiCode(Yandex, language)));
0425 
0426     d->currentReply = d->networkManager->get(QNetworkRequest(url));
0427 }
0428 
0429 void DOnlineTranslator::parseYandexTranslit(QString& text)
0430 {
0431     if ((quintptr)d->currentReply.data() != sender()->property("QNetworkReply").value<quintptr>())
0432     {
0433         return;
0434     }
0435 
0436     d->currentReply->deleteLater();
0437 
0438     if (d->currentReply->error() != QNetworkReply::NoError)
0439     {
0440         resetData(NetworkError, d->currentReply->errorString());
0441 
0442         return;
0443     }
0444 
0445     const QByteArray reply = d->currentReply->readAll();
0446 
0447 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0448 
0449     text += QString::fromUtf8(reply.mid(1).chopped(1));
0450 
0451 #else
0452 
0453     text += QString::fromUtf8(reply.mid(1));
0454     text.chop(1);
0455 
0456 #endif
0457 
0458 }
0459 
0460 } // namespace Digikam