diff --git a/extensions/bingtranslate.cpp b/extensions/bingtranslate.cpp index f935613..04691fb 100644 --- a/extensions/bingtranslate.cpp +++ b/extensions/bingtranslate.cpp @@ -1,6 +1,7 @@ -#include "extension.h" +#include "extension.h" +#include "defs.h" #include "text.h" -#include +#include "network.h" #include #include @@ -53,7 +54,7 @@ QStringList languages "Welsh: cy" }; -std::wstring translateTo; +std::wstring translateTo = L"en"; BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -82,49 +83,33 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved return TRUE; } -// This function detects language and puts it in translateFrom if it's empty -std::wstring Translate(std::wstring text, std::wstring& translateFrom, std::wstring translateTo) +// This function detects language and returns it if translateFrom is empty +std::wstring Translate(const std::wstring& text, std::wstring translateFrom, std::wstring translateTo) { static std::atomic internet = NULL; if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0); - char utf8[10000] = {}; - WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, utf8, 10000, NULL, NULL); - text.clear(); - for (int i = 0; utf8[i];) + std::wstring escapedText; + for (unsigned char ch : WideStringToString(text)) { - wchar_t utf8char[3] = {}; - swprintf_s<3>(utf8char, L"%02X", (int)(unsigned char)utf8[i++]); - text += L"%" + std::wstring(utf8char); + wchar_t escapedChar[4] = {}; + swprintf_s<4>(escapedChar, L"%%%02X", (int)ch); + escapedText = escapedChar; } + std::wstring location = translateFrom.empty() + ? L"/tdetect?text=" + text + : L"/ttranslate?from=" + translateFrom + L"&to=" + translateTo + L"&text=" + text; std::wstring translation; if (internet) - { - std::wstring location = translateFrom.empty() - ? L"/tdetect?text=" + text - : L"/ttranslate?from=" + translateFrom + L"&to=" + translateTo + L"&text=" + text; - if (HINTERNET connection = WinHttpConnect(internet, L"www.bing.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) - { - if (HINTERNET request = WinHttpOpenRequest(connection, L"POST", location.c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE)) - { + if (InternetHandle connection = WinHttpConnect(internet, L"www.bing.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) + if (InternetHandle request = WinHttpOpenRequest(connection, L"POST", location.c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE)) if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL)) - { - DWORD bytesRead; - char buffer[10000] = {}; - WinHttpReceiveResponse(request, NULL); - WinHttpReadData(request, buffer, 10000, &bytesRead); - wchar_t wbuffer[10000] = {}; - MultiByteToWideChar(CP_UTF8, 0, buffer, -1, wbuffer, 10000); - if (translateFrom.empty()) translateFrom = wbuffer; - // Response formatted as JSON: translation starts with :" and ends with "} - if (std::wcmatch results; std::regex_search(wbuffer, results, std::wregex(L":\"(.+)\"\\}"))) translation = results[1]; - } - WinHttpCloseHandle(request); - } - WinHttpCloseHandle(connection); - } - } + if (auto response = ReceiveHttpRequest(request)) + if (translateFrom.empty()) translation = response.value(); + else if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L":\"(.+)\"\\}"))) translation = results[1]; + + Escape(translation); return translation; } @@ -132,35 +117,20 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) { if (sentenceInfo["hook address"] == -1) return false; - { - static std::mutex m; - static std::vector requestTimes; - std::lock_guard l(m); - requestTimes.push_back(GetTickCount()); - requestTimes.erase(std::remove_if(requestTimes.begin(), requestTimes.end(), [&](DWORD requestTime) { return GetTickCount() - requestTime > 60 * 1000; }), requestTimes.end()); - if (!sentenceInfo["current select"] && requestTimes.size() > 30) - { - sentence += TOO_MANY_TRANS_REQUESTS; - return true; - } - } - - std::wstring translation, translateFrom; - Translate(sentence, translateFrom, translateTo); - translation = Translate(sentence, translateFrom, translateTo); - - for (int i = 0; i < translation.size(); ++i) - { - if (translation[i] == L'\\') - { - translation[i] = 0x200b; - if (translation[i + 1] == L'r') translation[i + 1] = 0x200b; // for some reason \r gets displayed as a newline - if (translation[i + 1] == L'n') translation[i + 1] = L'\n'; - if (translation[i + 1] == L't') translation[i + 1] = L'\t'; - } - } + static RateLimiter rateLimiter(30, 60 * 1000); + std::wstring translation; + if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS; + else translation = Translate(sentence, Translate(sentence, L"", translateTo), translateTo); if (translation.empty()) translation = TRANSLATION_ERROR; sentence += L"\n" + translation; return true; } + +TEST( + { + std::wstring test = L"こんにちは"; + ProcessSentence(test, SentenceInfo{ SentenceInfo::DUMMY }); + assert(test.find(L"Hello") != std::wstring::npos); + } +); diff --git a/extensions/extension.h b/extensions/extension.h index f7805e5..77d4c58 100644 --- a/extensions/extension.h +++ b/extensions/extension.h @@ -17,6 +17,8 @@ struct SentenceInfo for (auto info = infoArray; info->name != nullptr; ++info) if (propertyName == info->name) return info->value; throw; } + + inline static InfoForExtension DUMMY[2] = { { "hook address", 0 } }; }; struct SKIP {}; diff --git a/extensions/extensiontester.cpp b/extensions/extensiontester.cpp index c84f3a9..e34841a 100644 --- a/extensions/extensiontester.cpp +++ b/extensions/extensiontester.cpp @@ -8,4 +8,5 @@ int main() *(wcsrchr(path, L'\\') + 1) = 0; for (auto file : std::filesystem::directory_iterator(path)) if (file.path().extension() == L".dll") LoadLibraryW(file.path().c_str()); + Sleep(10000); } diff --git a/extensions/googletranslate.cpp b/extensions/googletranslate.cpp index 31ee689..384a3bd 100644 --- a/extensions/googletranslate.cpp +++ b/extensions/googletranslate.cpp @@ -1,6 +1,8 @@ -#include "extension.h" +#include "extension.h" +#include "defs.h" #include "text.h" -#include +#include "util.h" +#include "network.h" #include #include #include @@ -69,7 +71,7 @@ QStringList languages "Zulu: zu" }; -std::wstring translateTo; +std::wstring translateTo = L"en"; BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -98,19 +100,20 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved return TRUE; } -std::wstring GetTranslationUri(std::wstring text, unsigned TKK) +std::wstring GetTranslationUri(const std::wstring& text, unsigned TKK) { // If no TKK available, use this uri. Can't use too much or google will detect unauthorized access if (!TKK) return L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=" + translateTo + L"&q=" + text; // Artikash 8/19/2018: reverse engineered from translate.google.com - char utf8[10000] = {}; - WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, utf8, 10000, NULL, NULL); - - unsigned a = (unsigned)(_time64(NULL) / 3600), b = a; // <- the first part of TKK - for (int i = 0; utf8[i];) + std::wstring escapedText; + unsigned a = _time64(NULL) / 3600, b = a; // <- the first part of TKK + for (unsigned char ch : WideStringToString(text)) { - a += (unsigned char)utf8[i++]; + wchar_t escapedChar[4] = {}; + swprintf_s<4>(escapedChar, L"%%%02X", (int)ch); + escapedText += escapedChar; + a += ch; a += a << 10; a ^= a >> 6; } @@ -121,104 +124,49 @@ std::wstring GetTranslationUri(std::wstring text, unsigned TKK) a %= 1000000; b ^= a; - text.clear(); - for (int i = 0; utf8[i];) - { - wchar_t utf8char[3] = {}; - swprintf_s<3>(utf8char, L"%02X", (int)(unsigned char)utf8[i++]); - text += L"%" + std::wstring(utf8char); - } - - return L"/translate_a/single?client=t&dt=ld&dt=rm&dt=t&tl=" + translateTo + L"&tk=" + std::to_wstring(a) + L"." + std::to_wstring(b) + L"&q=" + text; + return L"/translate_a/single?client=t&dt=ld&dt=rm&dt=t&tl=" + translateTo + L"&tk=" + std::to_wstring(a) + L"." + std::to_wstring(b) + L"&q=" + escapedText; } bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) { if (sentenceInfo["hook address"] == -1) return false; - { - static std::mutex m; - static std::vector requestTimes; - std::lock_guard l(m); - requestTimes.push_back(GetTickCount()); - requestTimes.erase(std::remove_if(requestTimes.begin(), requestTimes.end(), [&](DWORD requestTime) { return GetTickCount() - requestTime > 60 * 1000; }), requestTimes.end()); - if (!sentenceInfo["current select"] && requestTimes.size() > 30) - { - sentence += TOO_MANY_TRANS_REQUESTS; - return true; - } - } - static std::atomic internet = NULL; if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0); static std::atomic TKK = 0; + static RateLimiter rateLimiter(30, 60 * 1000); + std::wstring translation; - if (internet) + if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS; + else if (internet) { if (!TKK) - { - if (HINTERNET connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) - { - if (HINTERNET request = WinHttpOpenRequest(connection, L"GET", L"/", NULL, NULL, NULL, WINHTTP_FLAG_SECURE)) - { + if (InternetHandle connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) + if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", L"/", NULL, NULL, NULL, WINHTTP_FLAG_SECURE)) if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL)) - { - DWORD bytesRead; - char buffer[200000] = {}; // Google Translate page is ~180kb (good god the bloat >_>) - WinHttpReceiveResponse(request, NULL); - WinHttpReadData(request, buffer, 200000, &bytesRead); - if (std::cmatch results; std::regex_search(buffer, results, std::regex("(\\d{7,})'"))) TKK = stoll(results[1]); - } - WinHttpCloseHandle(request); - } - WinHttpCloseHandle(connection); - } - } - + if (auto response = ReceiveHttpRequest(request)) + if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L"(\\d{7,})'"))) TKK = stoll(results[1]); - if (HINTERNET connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) - { - if (HINTERNET request = WinHttpOpenRequest(connection, L"GET", GetTranslationUri(sentence, TKK).c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE)) - { + if (InternetHandle connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0)) + if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", GetTranslationUri(sentence, TKK).c_str(), NULL, NULL, NULL, WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE)) if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL)) - { - DWORD bytesRead; - char buffer[10000] = {}; - WinHttpReceiveResponse(request, NULL); - WinHttpReadData(request, buffer, 10000, &bytesRead); - // Response formatted as JSON: starts with '[[["' - if (buffer[0] == '[') - { - wchar_t wbuffer[10000] = {}; - MultiByteToWideChar(CP_UTF8, 0, buffer, -1, wbuffer, 10000); - std::wstring response(wbuffer); - for (std::wsmatch results; std::regex_search(response, results, std::wregex(L"\\[\"(.*?)\",[n\"]")); response = results.suffix()) - translation += std::wstring(results[1]) + L" "; - - for (int i = 0; i < translation.size(); ++i) - { - if (translation[i] == L'\\') - { - translation[i] = 0x200b; - if (translation[i + 1] == L'r') translation[i + 1] = 0x200b; // for some reason \r gets displayed as a newline - if (translation[i + 1] == L'n') translation[i + 1] = L'\n'; - if (translation[i + 1] == L't') translation[i + 1] = L'\t'; - } - } - } - else - { - translation = TRANSLATION_ERROR + (L" (TKK=" + std::to_wstring(TKK) + L")"); - TKK = 0; - } - } - WinHttpCloseHandle(request); - } - WinHttpCloseHandle(connection); - } + if (auto response = ReceiveHttpRequest(request)) + if (response.value()[0] == L'[') + for (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L"\\[\"(.*?)\",[n\"]")); response = results.suffix()) + translation += std::wstring(results[1]) + L" "; + else std::tie(translation, TKK) = std::tuple(TRANSLATION_ERROR + (L" (TKK=" + std::to_wstring(TKK) + L")"), 0); } if (translation.empty()) translation = TRANSLATION_ERROR; + Escape(translation); sentence += L"\n" + translation; return true; } + +TEST( + { + std::wstring test = L"こんにちは"; + ProcessSentence(test, SentenceInfo{ SentenceInfo::DUMMY }); + assert(test.find(L"Hello") != std::wstring::npos); + } +); diff --git a/extensions/network.h b/extensions/network.h new file mode 100644 index 0000000..3131e91 --- /dev/null +++ b/extensions/network.h @@ -0,0 +1,39 @@ +#pragma once + +#include "util.h" +#include + +using InternetHandle = AutoHandle>; + +std::optional ReceiveHttpRequest(HINTERNET request) +{ + WinHttpReceiveResponse(request, NULL); + std::string data; + DWORD dwSize, dwDownloaded; + do + { + dwSize = 0; + WinHttpQueryDataAvailable(request, &dwSize); + if (!dwSize) break; + std::vector buffer(dwSize); + WinHttpReadData(request, buffer.data(), dwSize, &dwDownloaded); + data += std::string(buffer.data(), dwDownloaded); + } while (dwSize > 0); + + if (data.empty()) return {}; + return StringToWideString(data); +} + +void Escape(std::wstring& text) +{ + for (int i = 0; i < text.size(); ++i) + { + if (text[i] == L'\\') + { + text[i] = 0x200b; + if (text[i + 1] == L'r') text[i + 1] = 0x200b; // for some reason \r gets displayed as a newline + if (text[i + 1] == L'n') text[i + 1] = L'\n'; + if (text[i + 1] == L't') text[i + 1] = L'\t'; + } + } +} diff --git a/extensions/util.h b/extensions/util.h new file mode 100644 index 0000000..0d72277 --- /dev/null +++ b/extensions/util.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common.h" +#include "types.h" + +class RateLimiter +{ +public: + RateLimiter(int requests, int delay) : requestsLeft(requests), delay(delay) {} + bool Request() { CreateTimerQueueTimer(&DUMMY, timerQueue, [](void* This, BOOLEAN) { ++((RateLimiter*)This)->requestsLeft; }, this, delay, 0, 0); return --requestsLeft > 0; } + int delay; + +private: + std::atomic requestsLeft; + AutoHandle> timerQueue = CreateTimerQueue(); + HANDLE DUMMY; +}; + +inline std::wstring StringToWideString(const std::string& text) +{ + std::vector buffer(text.size() + 1); + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size()); + return buffer.data(); +} + +inline std::string WideStringToString(const std::wstring& text) +{ + std::vector buffer((text.size() + 1) * 4); + WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size(), nullptr, nullptr); + return buffer.data(); +} + +inline const std::wstring NEWLINE = L"\n"; diff --git a/include/defs.h b/include/defs.h index 17e02fe..fc220f8 100644 --- a/include/defs.h +++ b/include/defs.h @@ -42,7 +42,7 @@ inline void FORMAT_MESSAGE(const char* format, Args... args) } #ifdef _DEBUG -#define TEST(...) inline auto TEST__RUNNER__DUMMY = std::invoke([]{ __VA_ARGS__; return 0; }) +#define TEST(...) inline auto TEST__RUNNER__DUMMY = (CloseHandle(CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr)), 0); #else #define TEST(...) #endif diff --git a/text.cpp b/text.cpp index 0685bb5..d779c66 100644 --- a/text.cpp +++ b/text.cpp @@ -76,8 +76,7 @@ const char* COULD_NOT_FIND = u8"Textractor: could not find text"; const char* SELECT_LANGUAGE = u8"Select Language"; const char* BING_PROMPT = u8"What language should Bing translate to?"; const char* GOOGLE_PROMPT = u8"What language should Google translate to?"; -const wchar_t* TOO_MANY_TRANS_REQUESTS = LR"( -Too many translation requests: refuse to make more)"; +const wchar_t* TOO_MANY_TRANS_REQUESTS = L"Too many translation requests: refuse to make more"; const wchar_t* TRANSLATION_ERROR = L"Error while translating"; const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings Click and drag on window edges to move, or the bottom right corner to resize)"; @@ -208,8 +207,7 @@ Estoy buscando un nuevo trabajo: por favor envíame un correo si estás contrata SELECT_LANGUAGE = u8"Seleccionar lenguaje"; BING_PROMPT = u8"¿A qué idioma debe traducir Bing?"; GOOGLE_PROMPT = u8"¿A qué idioma debe traducir Google?"; - TOO_MANY_TRANS_REQUESTS = LR"( -Demasiadas peticiones de traducción: no se puede hacer más)"; + TOO_MANY_TRANS_REQUESTS = L"Demasiadas peticiones de traducción: no se puede hacer más"; TRANSLATION_ERROR = L"Error al traducir"; EXTRA_WINDOW_INFO = u8R"(Clic derecho para configurar Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior derecha para cambiar el tamaño)"; @@ -294,8 +292,7 @@ S/Q/V: 代码页/UTF-16/UTF-8 字符串 SELECT_LANGUAGE = u8"选择语言"; BING_PROMPT = u8"想要使用 Bing 翻译到哪种语言?"; GOOGLE_PROMPT = u8"想要使用 Google 翻译到哪种语言?"; - TOO_MANY_TRANS_REQUESTS = LR"( -太多翻译请求: 拒绝生成更多)"; + TOO_MANY_TRANS_REQUESTS = L"太多翻译请求: 拒绝生成更多"; TRANSLATION_ERROR = L"翻译时出错"; EXTRA_WINDOW_INFO = u8R"(右键修改设置 在窗口边缘点击并拖拽来移动,或在右下角点击并拖拽来调整大小)";