File indexing completed on 2024-12-22 04:40:17
0001 /* 0002 SPDX-FileCopyrightText: 2023 Mladen Milinkovic <max@smoothware.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "deeplengine.h" 0007 0008 #include "appglobal.h" 0009 #include "application.h" 0010 #include "helpers/common.h" 0011 #include "scconfig.h" 0012 0013 #include <QByteArray> 0014 #include <QDebug> 0015 #include <QFile> 0016 #include <QJsonArray> 0017 #include <QJsonDocument> 0018 #include <QJsonObject> 0019 #include <QNetworkAccessManager> 0020 #include <QNetworkReply> 0021 #include <QNetworkRequest> 0022 #include <QUrlQuery> 0023 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 0027 #include <memory> 0028 #include <type_traits> 0029 0030 #include <openssl/bio.h> 0031 #include <openssl/evp.h> 0032 #include <openssl/pem.h> 0033 0034 using namespace SubtitleComposer; 0035 0036 DeepLEngine::DeepLEngine(QObject *parent) 0037 : TranslateEngine(parent), 0038 m_netManager(new QNetworkAccessManager(this)), 0039 m_ui(new Ui::DeepLEngine) 0040 { 0041 } 0042 0043 DeepLEngine::~DeepLEngine() 0044 { 0045 delete m_ui; 0046 } 0047 0048 static void 0049 showError(QNetworkReply *res) 0050 { 0051 QString errText; 0052 const QJsonDocument doc = QJsonDocument::fromJson(res->readAll()); 0053 if(doc.isObject()) { 0054 const QJsonObject err = doc[$("error")].toObject(); 0055 if(!err.empty()) { 0056 errText = i18n("Remote service error %1 %2\n%3", 0057 err[$("code")].toInt(), 0058 err[$("status")].toString(), 0059 err[$("message")].toString()); 0060 } 0061 } 0062 if(errText.isEmpty()) { 0063 const int httpCode = res->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 0064 if(httpCode) { 0065 errText = i18n("HTTP Error %1 - %2\n%3", 0066 httpCode, 0067 res->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(), 0068 QString(res->readAll())); 0069 } else { 0070 errText = i18n("Network Error %1\n%2", 0071 int(res->error()), 0072 QString(res->readAll())); 0073 } 0074 } 0075 qWarning() << "DeepL Engine Error" << errText; 0076 KMessageBox::error(app()->mainWindow(), errText, i18n("DeepL Engine Error")); 0077 } 0078 0079 void 0080 DeepLEngine::settings(QWidget *widget) 0081 { 0082 m_ui->setupUi(widget); 0083 m_ui->authKey->setText(SCConfig::dltAuthKey()); 0084 m_ui->apiDomain->setText(SCConfig::dltApiDomain()); 0085 0086 connect(m_ui->btnConnect, &QPushButton::clicked, this, [&](){ 0087 SCConfig::setDltAuthKey(m_ui->authKey->text()); 0088 SCConfig::setDltApiDomain(m_ui->apiDomain->text()); 0089 languagesUpdate(); 0090 }); 0091 0092 m_ui->grpSettings->setEnabled(false); 0093 emit engineReady(false); 0094 0095 if(!SCConfig::dltAuthKey().isEmpty()) 0096 languagesUpdate(); 0097 } 0098 0099 bool 0100 DeepLEngine::languagesUpdate() 0101 { 0102 QNetworkRequest request; 0103 QNetworkReply *res; 0104 0105 request.setUrl($("https://%1/v2/languages?type=source").arg(SCConfig::dltApiDomain())); 0106 request.setRawHeader("Authorization", QByteArray("DeepL-Auth-Key ") + SCConfig::dltAuthKey().toUtf8()); 0107 res = m_netManager->get(request); 0108 connect(res, &QNetworkReply::finished, this, &DeepLEngine::languagesUpdated); 0109 0110 request.setUrl($("https://%1/v2/languages?type=target").arg(SCConfig::dltApiDomain())); 0111 request.setRawHeader("Authorization", QByteArray("DeepL-Auth-Key ") + SCConfig::dltAuthKey().toUtf8()); 0112 res = m_netManager->get(request); 0113 connect(res, &QNetworkReply::finished, this, &DeepLEngine::languagesUpdated); 0114 0115 return true; 0116 } 0117 0118 void 0119 DeepLEngine::languagesUpdated() 0120 { 0121 QNetworkReply *res = qobject_cast<QNetworkReply *>(sender()); 0122 const bool isSource = res->request().url().query().endsWith($("=source")); 0123 res->deleteLater(); 0124 0125 if(res->error() == QNetworkReply::NoError) { 0126 if(isSource) { 0127 m_ui->langSource->clear(); 0128 m_ui->langSource->addItem(i18n("Autodetect Language"), QString()); 0129 } else { 0130 m_ui->langTranslation->clear(); 0131 } 0132 0133 const QJsonDocument doc = QJsonDocument::fromJson(res->readAll()); 0134 const QJsonArray langs = doc.array(); 0135 for(auto it = langs.cbegin(); it != langs.cend(); ++it) { 0136 const QJsonObject o = it->toObject(); 0137 const QString langCode = o.value($("language")).toString(); 0138 const QString langTitle = o.value($("name")).toString()/*QLocale(langCode).nativeLanguageName()*/; 0139 const QString ttl = langCode % $(" - ") % langTitle; 0140 if(isSource) { 0141 m_ui->langSource->addItem(ttl, langCode); 0142 if(langCode == SCConfig::dltLangSource()) 0143 m_ui->langSource->setCurrentIndex(m_ui->langSource->count() - 1); 0144 } else { 0145 m_ui->langTranslation->addItem(ttl, langCode); 0146 if(langCode == SCConfig::dltLangTrans()) 0147 m_ui->langTranslation->setCurrentIndex(m_ui->langTranslation->count() - 1); 0148 } 0149 } 0150 m_ui->grpSettings->setEnabled(true); 0151 emit engineReady(true); 0152 } else { 0153 showError(res); 0154 } 0155 } 0156 0157 void 0158 DeepLEngine::translate(QVector<QString> &textLines) 0159 { 0160 SCConfig::setDltLangSource(m_ui->langSource->currentData().toString()); 0161 SCConfig::setDltLangTrans(m_ui->langTranslation->currentData().toString()); 0162 0163 QNetworkRequest request($("https://%1/v2/translate").arg(SCConfig::dltApiDomain())); 0164 request.setRawHeader("Authorization", QByteArray("DeepL-Auth-Key ") + SCConfig::dltAuthKey().toUtf8()); 0165 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); 0166 0167 QUrlQuery commonPost; 0168 if(!SCConfig::dltLangSource().isEmpty()) 0169 commonPost.addQueryItem($("source_lang"), SCConfig::dltLangSource()); 0170 commonPost.addQueryItem($("target_lang"), SCConfig::dltLangTrans()); 0171 const QByteArray commonPostData = commonPost.query(QUrl::FullyEncoded).toUtf8(); 0172 0173 int *reqCount = new int; 0174 auto translateDone = [=](){ 0175 if(--(*reqCount) == 0) { 0176 delete reqCount; 0177 emit translated(); 0178 } 0179 }; 0180 0181 int line = 0; 0182 *reqCount = 1; 0183 while(line != textLines.size()) { 0184 // Request body size must not exceed 128 KiB (128 ยท 1024 bytes) 0185 constexpr const int sizeLimit = 128 << 10; 0186 const int off = line; 0187 0188 QByteArray postData = commonPostData; 0189 for(;;) { 0190 static const char key[] = "&text="; 0191 const QByteArray val = QUrl::toPercentEncoding(textLines.at(line)); 0192 const QString &ln = textLines.at(line); 0193 if(postData.size() + sizeof(key) - 1 + ln.size() >= sizeLimit) 0194 break; 0195 postData.append(key, sizeof(key) - 1).append(val); 0196 if(++line == textLines.size()) 0197 break; 0198 } 0199 0200 (*reqCount)++; 0201 QNetworkReply *res = m_netManager->post(QNetworkRequest(request), postData); 0202 connect(res, &QNetworkReply::finished, this, [=, &textLines](){ 0203 res->deleteLater(); 0204 if(res->error() == QNetworkReply::NoError) { 0205 const QJsonDocument doc = QJsonDocument::fromJson(res->readAll()); 0206 const QJsonArray tta = doc[$("translations")].toArray(); 0207 for(int i = 0, n = tta.size(); i < n; i++) 0208 textLines[off + i] = tta.at(i).toObject().value($("text")).toString(); 0209 } else { 0210 showError(res); 0211 } 0212 translateDone(); 0213 }); 0214 } 0215 translateDone(); 0216 }