add devtools api and new deepl extension

Added the DevTools API and the wrapper with  the use of the QtWebSockets library.
Added a new DeepL extension which uses the DevTools API.
This commit is contained in:
zeheyler 2020-10-14 03:37:03 +03:00
parent fe1cdfc947
commit 6b54ec0733
7 changed files with 735 additions and 5 deletions

View File

@ -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%$<SEMICOLON>${qt5_install_prefix}/bin
COMMAND Qt5::windeployqt --dir $<TARGET_FILE_DIR:${PROJECT_NAME}> "$<TARGET_FILE_DIR:${PROJECT_NAME}>/DevTools\ DeepL\ Translate.dll"
)
endif()

278
extensions/devtools.cpp Normal file
View File

@ -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<QAbstractSocket::SocketState>();
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<QJsonObject> 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);
}

49
extensions/devtools.h Normal file
View File

@ -0,0 +1,49 @@
#include <fstream>
#include <QtCore>
#include <QtWebSockets/QWebSocket>
#include <ppltasks.h>
#include "network.h"
using namespace Concurrency;
typedef std::map<long, task_completion_event<QJsonObject>> 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;
};

View File

@ -0,0 +1,195 @@
#include "qtcommon.h"
#include "extension.h"
#include "network.h"
#include "devtools.h"
extern const wchar_t* TRANSLATION_ERROR;
extern Synchronized<std::wstring> 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<bool, std::wstring> 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("<div></div>");
// 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) };
}
}

View File

@ -0,0 +1,194 @@
#include "qtcommon.h"
#include "extension.h"
#include "blockmarkup.h"
#include "network.h"
#include <map>
#include <fstream>
#include <QComboBox>
#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<bool, std::wstring> 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<std::wstring> translateTo = L"en";
Synchronized<std::map<std::wstring, std::wstring>> 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<bool&, const char*>{
{ 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<int&, const char*>{
{ 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<int>(&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<std::wstring>{
{ 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<std::wstring_view>{ 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;
}

View File

@ -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<HINTERNET> 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))
{

View File

@ -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; }