diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 751ec15..e7367d0 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -1,7 +1,7 @@ include(QtUtils) msvc_registry_search() -find_qt5(Core Widgets) - +find_qt5(Core Widgets WebSockets) +set(CMAKE_AUTOMOC ON) cmake_policy(SET CMP0037 OLD) add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) @@ -10,6 +10,7 @@ add_library(DeepL\ Translate MODULE deepltranslate.cpp translatewrapper.cpp netw add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp) add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp) add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) +add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtoolswrapper.cpp devtools.cpp network.cpp extensionimpl.cpp) add_library(Lua MODULE lua.cpp extensionimpl.cpp) add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp) add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp) @@ -25,6 +26,7 @@ target_precompile_headers(DeepL\ Translate REUSE_FROM pch) target_precompile_headers(Extra\ Newlines REUSE_FROM pch) target_precompile_headers(Extra\ Window REUSE_FROM pch) target_precompile_headers(Google\ Translate REUSE_FROM pch) +target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch) target_precompile_headers(Lua REUSE_FROM pch) target_precompile_headers(Regex\ Filter REUSE_FROM pch) target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch) @@ -38,6 +40,16 @@ target_link_libraries(Bing\ Translate winhttp Qt5::Widgets) target_link_libraries(DeepL\ Translate winhttp Qt5::Widgets) target_link_libraries(Extra\ Window Qt5::Widgets) target_link_libraries(Google\ Translate winhttp Qt5::Widgets) +target_link_libraries(DevTools\ DeepL\ Translate winhttp Qt5::Widgets Qt5::WebSockets) target_link_libraries(Lua lua53 Qt5::Widgets) target_link_libraries(Regex\ Filter Qt5::Widgets) target_link_libraries(Thread\ Linker Qt5::Widgets) + +if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll) + add_custom_command(TARGET DevTools\ DeepL\ Translate + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" + COMMAND set PATH=%PATH%$${qt5_install_prefix}/bin + COMMAND Qt5::windeployqt --dir $ "$/DevTools\ DeepL\ Translate.dll" + ) +endif() \ No newline at end of file diff --git a/extensions/devtools.cpp b/extensions/devtools.cpp new file mode 100644 index 0000000..f81828e --- /dev/null +++ b/extensions/devtools.cpp @@ -0,0 +1,278 @@ +#include "devtools.h" + +DevTools::DevTools(QObject* parent) : + QObject(parent), + idcounter(0), + pagenavigated(false), + translateready(false), + status("Stopped"), + session(1) +{ +} + +void DevTools::startDevTools(QString path, bool headless, int port) +{ + if (startChrome(path, headless, port)) + { + QString webSocketDebuggerUrl; + if (GetwebSocketDebuggerUrl(webSocketDebuggerUrl, port)) + { + connect(&webSocket, &QWebSocket::stateChanged, this, &DevTools::stateChanged); + connect(&webSocket, &QWebSocket::textMessageReceived, this, &DevTools::onTextMessageReceived); + webSocket.open(webSocketDebuggerUrl); + session += 1; + } + else + { + status = "Failed to find chrome debug port!"; + emit statusChanged(status); + } + + } + else + { + status = "Failed to start chrome!"; + emit statusChanged(status); + } +} + +int DevTools::getSession() +{ + return session; +} + +QString DevTools::getStatus() +{ + return status; +} + +DevTools::~DevTools() +{ + closeDevTools(); +} + +bool DevTools::startChrome(QString path, bool headless, int port) +{ + if (!std::filesystem::exists(path.toStdWString())) + return false; + DWORD exitCode = 0; + if ((GetExitCodeProcess(processInfo.hProcess, &exitCode) != FALSE) && (exitCode == STILL_ACTIVE)) + return false; + QString args = "--proxy-server=direct:// --disable-extensions --disable-gpu --user-data-dir=" + + QString::fromStdWString(std::filesystem::current_path()) + + "\\devtoolscache --remote-debugging-port=" + + QString::number(port); + if (headless) + args += " --headless"; + STARTUPINFOW dummy = { sizeof(dummy) }; + if (!CreateProcessW(NULL, (wchar_t*)(path + " " + args).utf16(), nullptr, nullptr, + FALSE, 0, nullptr, nullptr, &dummy, &processInfo)) + return false; + else + return true; +} + +bool DevTools::GetwebSocketDebuggerUrl(QString& url, int port) +{ + url.clear(); + if (HttpRequest httpRequest{ + L"Mozilla/5.0 Textractor", + L"127.0.0.1", + L"POST", + FormatString(L"/json/list").c_str(), + "", + NULL, + NULL, + WINHTTP_FLAG_ESCAPE_DISABLE, + NULL, + NULL, + DWORD(port) + }) + { + QString qtString = QString::fromStdWString(httpRequest.response); + QJsonDocument doc = QJsonDocument::fromJson(qtString.toUtf8()); + QJsonArray rootObject = doc.array(); + + for (const auto obj : rootObject) + if (obj.toObject().value("type") == "page") + { + url.append(obj.toObject().value("webSocketDebuggerUrl").toString()); + break; + } + if (!url.isEmpty()) + return true; + else + return false; + } + else + return false; + +} + +void DevTools::stateChanged(QAbstractSocket::SocketState state) +{ + QMetaEnum metaenum = QMetaEnum::fromType(); + status = metaenum.valueToKey(state); + emit statusChanged(status); +} + +void DevTools::setNavigated(bool value) +{ + mutex.lock(); + pagenavigated = value; + mutex.unlock(); +} + +bool DevTools::getNavigated() +{ + return pagenavigated; +} + +void DevTools::setTranslate(bool value) +{ + mutex.lock(); + translateready = value; + mutex.unlock(); +} + +bool DevTools::getTranslate() +{ + return translateready; +} + +bool DevTools::SendRequest(QString method, QJsonObject params, QJsonObject& root) +{ + if (!isConnected()) + return false; + root = QJsonObject(); + QJsonObject json; + task_completion_event response; + long id = idIncrement(); + json.insert("id", id); + json.insert("method", method); + if (!params.isEmpty()) + json.insert("params", params); + QJsonDocument doc(json); + QString message(doc.toJson(QJsonDocument::Compact)); + mutex.lock(); + mapqueue.insert(std::make_pair(id, response)); + mutex.unlock(); + webSocket.sendTextMessage(message); + webSocket.flush(); + try + { + root = create_task(response).get(); + } + catch (const std::exception& ex) + { + response.set_exception(ex); + return false; + } + if (!root.isEmpty()) + { + if (root.contains("error")) + { + return false; + } + else if (root.contains("result")) + return true; + else + return false; + } + else + return false; +} + +long DevTools::idIncrement() +{ + return ++idcounter; +} + +bool DevTools::isConnected() +{ + if (webSocket.state() == QAbstractSocket::ConnectedState) + return true; + else + return false; +} + +void DevTools::onTextMessageReceived(QString message) +{ + QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8()); + if (doc.isObject()) + { + QJsonObject root = doc.object(); + if (root.contains("method")) + { + if (root.value("method").toString() == "Page.navigatedWithinDocument") + { + mutex.lock(); + pagenavigated = true; + mutex.unlock(); + } + if (root.value("method").toString() == "DOM.attributeModified") + { + if (root.value("params").toObject().value("value") == "lmt__mobile_share_container") + { + mutex.lock(); + translateready = true; + mutex.unlock(); + } + } + return; + + } + if (root.contains("id")) + { + long id = root.value("id").toInt(); + MapResponse::iterator request = mapqueue.find(id); + if (request != mapqueue.end()) + { + request->second.set(root); + mutex.lock(); + mapqueue.erase(request); + mutex.unlock(); + } + return; + } + } +} + +void DevTools::closeDevTools() +{ + if (this->mapqueue.size() > 0) + { + MapResponse::iterator iter = this->mapqueue.begin(); + MapResponse::iterator iend = this->mapqueue.end(); + for (; iter != iend; iter++) + { + iter->second.set_exception("exception"); + } + } + webSocket.close(); + mapqueue.clear(); + idcounter = 0; + + DWORD exitCode = 0; + if (GetExitCodeProcess(processInfo.hProcess, &exitCode) != FALSE) + { + if (exitCode == STILL_ACTIVE) + { + TerminateProcess(processInfo.hProcess, 0); + WaitForSingleObject(processInfo.hProcess, 100); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + try + { + std::filesystem::remove_all(L"devtoolscache"); + } + catch (const std::exception&) + { + + } + } + status = "Stopped"; + emit statusChanged(status); +} diff --git a/extensions/devtools.h b/extensions/devtools.h new file mode 100644 index 0000000..4b036f5 --- /dev/null +++ b/extensions/devtools.h @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include "network.h" + +using namespace Concurrency; + +typedef std::map> MapResponse; + +class DevTools : public QObject { + Q_OBJECT +public: + explicit DevTools(QObject* parent = nullptr); + ~DevTools(); + +Q_SIGNALS: + void statusChanged(const QString &); + +private Q_SLOTS: + void stateChanged(QAbstractSocket::SocketState state); + void onTextMessageReceived(QString message); + +public: + void startDevTools(QString path, bool headless = false, int port = 9222); + void closeDevTools(); + void setNavigated(bool value); + bool getNavigated(); + void setTranslate(bool value); + bool getTranslate(); + int getSession(); + bool SendRequest(QString command, QJsonObject params, QJsonObject& result); + QString getStatus(); + +private: + bool isConnected(); + bool startChrome(QString path, bool headless = false, int port = 9222); + bool GetwebSocketDebuggerUrl(QString& url, int port = 9222); + long idIncrement(); + int session; + QWebSocket webSocket; + std::mutex mutex; + MapResponse mapqueue; + bool pagenavigated; + bool translateready; + long idcounter; + PROCESS_INFORMATION processInfo; + QString status; +}; \ No newline at end of file diff --git a/extensions/devtoolsdeepltranslate.cpp b/extensions/devtoolsdeepltranslate.cpp new file mode 100644 index 0000000..6b692f7 --- /dev/null +++ b/extensions/devtoolsdeepltranslate.cpp @@ -0,0 +1,195 @@ +#include "qtcommon.h" +#include "extension.h" +#include "network.h" +#include "devtools.h" + +extern const wchar_t* TRANSLATION_ERROR; +extern Synchronized translateTo; + +bool useCache = true, autostartchrome = false, headlesschrome = true; +int maxSentenceSize = 500, chromeport = 9222; + +const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate"; +QString URL = "https://www.deepl.com/en/translator"; +QStringList languages +{ + "Chinese (simplified): zh", + "Dutch: nl", + "English: en", + "French: fr", + "German: de", + "Italian: it", + "Japanese: ja", + "Polish: pl", + "Portuguese: pt", + "Russian: ru", + "Spanish: es", +}; + +int docfound = -1; +int targetNodeId = -1; +int session = -1; + +std::pair Translate(const std::wstring& text, DevTools* devtools) +{ + if (devtools->getStatus() == "Stopped") + { + return { false, FormatString(L"Error: chrome not started") }; + } + if ((devtools->getStatus().startsWith("Fail")) || (devtools->getStatus().startsWith("Unconnected"))) + { + return { false, FormatString(L"Error: %s", S(devtools->getStatus())) }; + } + if (session != devtools->getSession()) + { + session = devtools->getSession(); + docfound = -1; + targetNodeId = -1; + } + + QString qtext = S(text); + + // Check text for repeated symbols (e.g. only ellipsis) + if (qtext.length() > 2) + for (int i = 1; i < (qtext.length() - 1); i++) + { + if (qtext[i] != qtext[1]) + break; + if ((i + 2) == qtext.length() && (qtext.front() == qtext.back())) + { + return { false, text }; + } + } + + // Add spaces near ellipsis for better translation and check for quotes + qtext.replace(QRegularExpression("[" + QString(8230) + "]" + "[" + QString(8230) + "]" + "[" + QString(8230) + "]"), QString(8230)); + qtext.replace(QRegularExpression("[" + QString(8230) + "]" + "[" + QString(8230) + "]"), QString(8230)); + qtext.replace(QRegularExpression("[" + QString(8230) + "]"), " " + QString(8230) + " "); + bool checkquote = false; + if ((qtext.front() == QString(12300)) && (qtext.back() == QString(12301))) + { + checkquote = true; + qtext.remove(0, 1); + qtext.chop(1); + } + QJsonObject root; + QJsonObject result; + + // Enable page feedback + if (!devtools->SendRequest("Page.enable", {}, root)) + { + return { false, FormatString(L"Error: page enable failed! %s", TRANSLATION_ERROR) }; + } + + // Navigate to site + QString fullurl = URL + "#ja/" + S(translateTo.Copy()) + "/" + qtext; + devtools->setNavigated(false); + devtools->setTranslate(false); + if (devtools->SendRequest("Page.navigate", { {"url", fullurl} }, root)) + { + // Wait until page is loaded + float timer = 0; + int timer_stop = 10; + while (!devtools->getNavigated() && timer < timer_stop) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timer += 0.1; + } + if (timer >= timer_stop) + { + return { false, FormatString(L"Error: page load timeout %d s! %s", timer_stop, TRANSLATION_ERROR) }; + } + QString OuterHTML("
"); + + // Get document + if (docfound == -1) + { + if (!devtools->SendRequest("DOM.getDocument", {}, root)) + { + docfound = -1; + return { false, FormatString(L"Error: getDocument failed! %s", TRANSLATION_ERROR) }; + } + docfound = root.value("result").toObject().value("root").toObject().value("nodeId").toInt(); + } + + //Get target selector + if (targetNodeId == -1) + { + if (!(devtools->SendRequest("DOM.querySelector", { {"nodeId", docfound}, {"selector", "textarea.lmt__target_textarea"} }, root)) + || (root.value("result").toObject().value("nodeId").toInt() == 0)) + { + docfound = -1; + return { false, FormatString(L"Error: querySelector result failed! %s", TRANSLATION_ERROR) }; + } + targetNodeId = root.value("result").toObject().value("nodeId").toInt(); + } + + // Wait for translation to appear on the web page + timer = 0; + while (!devtools->getTranslate() && timer < timer_stop) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timer += 0.1; + } + if (timer >= timer_stop) + { + // Catch notification if timeout + int noteNodeId = -1; + if (!(devtools->SendRequest("DOM.querySelector", { {"nodeId", docfound}, {"selector", "div.lmt__system_notification"} }, root)) + || (root.value("result").toObject().value("nodeId").toInt() == 0)) + { + return { false, FormatString(L"Error: result timeout %d s! %s", timer_stop, TRANSLATION_ERROR) }; + } + noteNodeId = root.value("result").toObject().value("nodeId").toInt(); + + if (devtools->SendRequest("DOM.getOuterHTML", { {"nodeId", noteNodeId + 1} }, root)) + { + OuterHTML = root.value("result").toObject().value("outerHTML").toString(); + OuterHTML.remove(QRegExp("<[^>]*>")); + OuterHTML = OuterHTML.trimmed(); + } + else + { + OuterHTML = "Could not get notification"; + } + return { false, FormatString(L"Error: got notification from translator: %s", S(OuterHTML)) }; + + } + + // Catch the translation + devtools->SendRequest("DOM.getOuterHTML", { {"nodeId", targetNodeId + 1} }, root); + result = root.value("result").toObject(); + OuterHTML = result.value("outerHTML").toString(); + OuterHTML.remove(QRegExp("<[^>]*>")); + OuterHTML = OuterHTML.trimmed(); + + // Check if the translator output language does not match the selected language + if (devtools->SendRequest("DOM.getAttributes", { {"nodeId", targetNodeId} }, root)) + { + QJsonObject result = root.value("result").toObject(); + QJsonArray attributes = result.value("attributes").toArray(); + for (size_t i = 0; i < attributes.size(); i++) + { + if (attributes[i].toString() == "lang") + { + QString targetlang = attributes[i + 1].toString().mid(0, 2); + if (targetlang != S(translateTo.Copy())) + { + return { false, FormatString(L"Error: target langs do not match (%s): %s", S(targetlang), S(OuterHTML)) }; + } + } + } + } + + // Get quotes back + if (checkquote) + { + OuterHTML = "\"" + OuterHTML + "\""; + } + return { true, S(OuterHTML) }; + } + else + { + return { false, FormatString(L"Error: navigate failed! %s", TRANSLATION_ERROR) }; + } +} diff --git a/extensions/devtoolswrapper.cpp b/extensions/devtoolswrapper.cpp new file mode 100644 index 0000000..5044e38 --- /dev/null +++ b/extensions/devtoolswrapper.cpp @@ -0,0 +1,194 @@ +#include "qtcommon.h" +#include "extension.h" +#include "blockmarkup.h" +#include "network.h" +#include +#include +#include +#include "devtools.h" + +extern const char* NATIVE_LANGUAGE; +extern const char* TRANSLATE_TO; +extern const char* TRANSLATE_SELECTED_THREAD_ONLY; +extern const char* USE_TRANS_CACHE; +extern const char* MAX_SENTENCE_SIZE; +extern const char* TRANSLATION_PROVIDER; +extern QStringList languages; +const char* PATH_TO_CHROME = u8"Path to chrome"; +const char* AUTO_START_CHROME = u8"Start chrome automatically"; +const char* HEADLESS_CHROME = u8"Start in headless mode"; +const char* CHROME_DEBUG_PORT = u8"Chrome debug port"; +const char* DEV_TOOLS_STATUS = u8"Status: "; +const char* START_DEV_TOOLS_BUTTON = u8"Start"; +const char* START_DEV_TOOLS = u8"Start chrome"; +const char* STOP_DEV_TOOLS_BUTTON = u8"Stop"; +const char* STOP_DEV_TOOLS = u8"Stop chrome"; + +extern bool useCache, autostartchrome, headlesschrome; +extern int maxSentenceSize, chromeport; + +std::pair Translate(const std::wstring& text, DevTools* devtools); + +const char* LANGUAGE = u8"Language"; +const std::string TRANSLATION_CACHE_FILE = FormatString("%s Cache.txt", TRANSLATION_PROVIDER); + +QFormLayout* display; +QSettings settings = openSettings(); +Synchronized translateTo = L"en"; +Synchronized> translationCache; + +int savedSize; +DevTools* devtools = nullptr; +std::wstring pathtochrome = L""; + +void SaveCache() +{ + std::wstring allTranslations(L"\xfeff"); + for (const auto& [sentence, translation] : translationCache.Acquire().contents) + allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n"); + std::ofstream(TRANSLATION_CACHE_FILE, std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t)); + savedSize = translationCache->size(); +} + +class Window : public QDialog +{ +public: + Window() : + QDialog(nullptr, Qt::WindowMinMaxButtonsHint) + { + display = new QFormLayout(this); + settings.beginGroup(TRANSLATION_PROVIDER); + + auto languageBox = new QComboBox(this); + languageBox->addItems(languages); + int language = -1; + if (settings.contains(LANGUAGE)) language = languageBox->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith); + if (language < 0) language = languageBox->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith); + if (language < 0) language = languageBox->findText("English", Qt::MatchStartsWith); + languageBox->setCurrentIndex(language); + saveLanguage(languageBox->currentText()); + display->addRow(TRANSLATE_TO, languageBox); + connect(languageBox, &QComboBox::currentTextChanged, this, &Window::saveLanguage); + for (auto [value, label] : Array{ + { useCache, USE_TRANS_CACHE }, + { autostartchrome, AUTO_START_CHROME }, + //{ headlesschrome, HEADLESS_CHROME } + }) + { + value = settings.value(label, value).toBool(); + auto checkBox = new QCheckBox(this); + checkBox->setChecked(value); + display->addRow(label, checkBox); + connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); }); + } + for (auto [value, label] : Array{ + { maxSentenceSize, MAX_SENTENCE_SIZE }, + { chromeport, CHROME_DEBUG_PORT }, + }) + { + value = settings.value(label, value).toInt(); + auto spinBox = new QSpinBox(this); + spinBox->setRange(0, INT_MAX); + spinBox->setValue(value); + display->addRow(label, spinBox); + connect(spinBox, qOverload(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); }); + } + + auto keyInput = new QLineEdit(settings.value(PATH_TO_CHROME).toString()); + pathtochrome = (S(keyInput->text())); + if (pathtochrome.empty()) + { + for (auto defaultpath : Array{ + { L"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" }, + { L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" }, + }) + if (std::filesystem::exists(defaultpath)) + { + pathtochrome = defaultpath; + keyInput->setText(S(pathtochrome)); + } + } + connect(keyInput, &QLineEdit::textChanged, [keyInput](QString key) { settings.setValue(PATH_TO_CHROME, S(pathtochrome = (S(key)))); }); + display->addRow(PATH_TO_CHROME, keyInput); + + connect(&startButton, &QPushButton::clicked, this, &Window::start); + connect(&stopButton, &QPushButton::clicked, this, &Window::stop); + display->addRow(START_DEV_TOOLS, &startButton); + display->addRow(STOP_DEV_TOOLS, &stopButton); + + status.setFrameStyle(QFrame::Panel | QFrame::Sunken); + display->addRow(DEV_TOOLS_STATUS, &status); + + setWindowTitle(TRANSLATION_PROVIDER); + QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection); + + std::ifstream stream(TRANSLATION_CACHE_FILE, std::ios::binary); + BlockMarkupIterator savedTranslations(stream, Array{ L"|SENTENCE|", L"|TRANSLATION|" }); + auto translationCache = ::translationCache.Acquire(); + + while (auto read = savedTranslations.Next()) + { + auto& [sentence, translation] = read.value(); + translationCache->try_emplace(std::move(sentence), std::move(translation)); + } + savedSize = translationCache->size(); + + devtools = new DevTools(this); + connect(devtools, &DevTools::statusChanged, [this](QString text) + { + status.setText(text); + }); + + if (autostartchrome) + QMetaObject::invokeMethod(this, &Window::start, Qt::QueuedConnection); + } + + ~Window() + { + stop(); + if (devtools != nullptr) + delete devtools; + SaveCache(); + } + +private: + void start() + { + if (devtools->getStatus() == "Stopped") + devtools->startDevTools(S(pathtochrome), headlesschrome, chromeport); + } + void stop() + { + devtools->closeDevTools(); + } + + + void saveLanguage(QString language) + { + settings.setValue(LANGUAGE, S(translateTo->assign(S(language.split(": ")[1])))); + } + QPushButton startButton{ START_DEV_TOOLS_BUTTON, this }; + QPushButton stopButton{ STOP_DEV_TOOLS_BUTTON, this }; + QLabel status{ "Stopped" }; +} window; + +bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) +{ + if (sentenceInfo["text number"] == 0 || sentence.size() > maxSentenceSize) return false; + + bool cache = false; + std::wstring translation; + if (useCache) + { + auto translationCache = ::translationCache.Acquire(); + if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second + L"\x200b"; + } + if (translation.empty() && (sentenceInfo["current select"])) + std::tie(cache, translation) = Translate(sentence, devtools); + if (cache) translationCache->try_emplace(sentence, translation); + if (cache && translationCache->size() > savedSize + 50) SaveCache(); + + JSON::Unescape(translation); + sentence += L"\n" + translation; + return true; +} \ No newline at end of file diff --git a/extensions/network.cpp b/extensions/network.cpp index abd4637..ca3cd76 100644 --- a/extensions/network.cpp +++ b/extensions/network.cpp @@ -10,13 +10,14 @@ HttpRequest::HttpRequest( const wchar_t* referrer, DWORD requestFlags, const wchar_t* httpVersion, - const wchar_t** acceptTypes + const wchar_t** acceptTypes, + DWORD port ) { static std::atomic internet = NULL; if (!internet) internet = WinHttpOpen(agentName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0); if (internet) - if (InternetHandle connection = WinHttpConnect(internet, serverName, INTERNET_DEFAULT_PORT, 0)) + if (InternetHandle connection = WinHttpConnect(internet, serverName, port, 0)) if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags)) if (WinHttpSendRequest(request, headers, -1UL, body.empty() ? NULL : body.data(), body.size(), body.size(), NULL)) { diff --git a/extensions/network.h b/extensions/network.h index d01ab3e..27ae6a4 100644 --- a/extensions/network.h +++ b/extensions/network.h @@ -16,7 +16,8 @@ struct HttpRequest const wchar_t* referrer = NULL, DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE, const wchar_t* httpVersion = NULL, - const wchar_t** acceptTypes = NULL + const wchar_t** acceptTypes = NULL, + DWORD port = INTERNET_DEFAULT_PORT ); operator bool() { return errorCode == ERROR_SUCCESS; }