forked from Public-Mirror/Textractor
reorganize translation
This commit is contained in:
parent
e7fff79f8f
commit
76804dd0aa
@ -4,11 +4,11 @@ find_qt5(Core Widgets)
|
|||||||
|
|
||||||
cmake_policy(SET CMP0037 OLD)
|
cmake_policy(SET CMP0037 OLD)
|
||||||
|
|
||||||
add_library(Bing\ Translate MODULE bingtranslate.cpp extensionimpl.cpp)
|
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
|
||||||
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
|
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
|
||||||
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
|
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
|
||||||
add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp)
|
add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp)
|
||||||
add_library(Google\ Translate MODULE googletranslate.cpp extensionimpl.cpp)
|
add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
|
||||||
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
|
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
|
||||||
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
|
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
|
||||||
add_library(Remove\ Repetition MODULE removerepeat.cpp extensionimpl.cpp)
|
add_library(Remove\ Repetition MODULE removerepeat.cpp extensionimpl.cpp)
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include <QInputDialog>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
extern const char* SELECT_LANGUAGE;
|
|
||||||
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
extern const char* BING_PROMPT;
|
|
||||||
|
|
||||||
|
const char* TRANSLATION_PROVIDER = "Bing";
|
||||||
QStringList languages
|
QStringList languages
|
||||||
{
|
{
|
||||||
"English: en",
|
"English: en",
|
||||||
@ -56,85 +53,29 @@ QStringList languages
|
|||||||
"Vietnamese: vi",
|
"Vietnamese: vi",
|
||||||
"Welsh: cy"
|
"Welsh: cy"
|
||||||
};
|
};
|
||||||
|
Synchronized<std::wstring> translateTo = L"en";
|
||||||
|
|
||||||
std::wstring translateTo = L"en";
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
|
|
||||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
|
||||||
{
|
{
|
||||||
switch (ul_reason_for_call)
|
std::wstring translateFrom;
|
||||||
{
|
if (HttpRequest httpRequest{
|
||||||
case DLL_PROCESS_ATTACH:
|
L"Mozilla/5.0 Textractor",
|
||||||
{
|
L"www.bing.com",
|
||||||
QTimer::singleShot(0, []
|
L"POST",
|
||||||
{
|
FormatString(L"/tdetect?text=%s", Escape(text)).c_str(),
|
||||||
translateTo = QInputDialog::getItem(
|
WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE,
|
||||||
nullptr,
|
}) translateFrom = httpRequest.response;
|
||||||
SELECT_LANGUAGE,
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
BING_PROMPT,
|
|
||||||
languages,
|
if (HttpRequest httpRequest{
|
||||||
0, false, nullptr,
|
L"Mozilla/5.0 Textractor",
|
||||||
Qt::WindowCloseButtonHint
|
L"www.bing.com",
|
||||||
).split(" ")[1].toStdWString();
|
L"POST",
|
||||||
});
|
FormatString(L"/ttranslate?from=%s&to=%s&text=%s", translateFrom, translateTo->c_str(), Escape(text)).c_str(),
|
||||||
}
|
WINHTTP_FLAG_ESCAPE_DISABLE | WINHTTP_FLAG_SECURE,
|
||||||
break;
|
})
|
||||||
case DLL_PROCESS_DETACH:
|
// Response formatted as JSON: translation starts with :" and ends with "}
|
||||||
{
|
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L":\"(.+)\"\\}"))) return { true, results[1] };
|
||||||
}
|
else return { false, TRANSLATION_ERROR };
|
||||||
break;
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<HINTERNET> internet = NULL;
|
|
||||||
if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
|
|
||||||
|
|
||||||
std::wstring escapedText;
|
|
||||||
for (unsigned char ch : WideStringToString(text))
|
|
||||||
{
|
|
||||||
wchar_t escapedChar[4] = {};
|
|
||||||
swprintf_s<4>(escapedChar, L"%%%02X", (int)ch);
|
|
||||||
escapedText += escapedChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring location = translateFrom.empty()
|
|
||||||
? L"/tdetect?text=" + escapedText
|
|
||||||
: L"/ttranslate?from=" + translateFrom + L"&to=" + translateTo + L"&text=" + escapedText;
|
|
||||||
std::wstring translation;
|
|
||||||
if (internet)
|
|
||||||
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))
|
|
||||||
if (auto response = ReceiveHttpRequest(request))
|
|
||||||
if (translateFrom.empty()) translation = response.value();
|
|
||||||
// Response formatted as JSON: translation starts with :" and ends with "}
|
|
||||||
else if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L":\"(.+)\"\\}"))) translation = results[1];
|
|
||||||
|
|
||||||
Unescape(translation);
|
|
||||||
return translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|
||||||
{
|
|
||||||
if (sentenceInfo["text number"] == 0) return false;
|
|
||||||
|
|
||||||
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::DUMMY });
|
|
||||||
assert(test.find(L"Hello") != std::wstring::npos);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "util.h"
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "util.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <QInputDialog>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
extern const char* SELECT_LANGUAGE;
|
|
||||||
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
extern const char* GOOGLE_PROMPT;
|
|
||||||
|
|
||||||
|
const char* TRANSLATION_PROVIDER = "Google";
|
||||||
QStringList languages
|
QStringList languages
|
||||||
{
|
{
|
||||||
"English: en",
|
"English: en",
|
||||||
@ -73,49 +70,19 @@ QStringList languages
|
|||||||
"Yiddish: yi",
|
"Yiddish: yi",
|
||||||
"Zulu: zu"
|
"Zulu: zu"
|
||||||
};
|
};
|
||||||
|
Synchronized<std::wstring> translateTo = L"en";
|
||||||
std::wstring translateTo = L"en";
|
|
||||||
|
|
||||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
|
||||||
{
|
|
||||||
switch (ul_reason_for_call)
|
|
||||||
{
|
|
||||||
case DLL_PROCESS_ATTACH:
|
|
||||||
{
|
|
||||||
QTimer::singleShot(0, []
|
|
||||||
{
|
|
||||||
translateTo = QInputDialog::getItem(
|
|
||||||
nullptr,
|
|
||||||
SELECT_LANGUAGE,
|
|
||||||
GOOGLE_PROMPT,
|
|
||||||
languages,
|
|
||||||
0, false, nullptr,
|
|
||||||
Qt::WindowCloseButtonHint
|
|
||||||
).split(" ")[1].toStdWString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DLL_PROCESS_DETACH:
|
|
||||||
{
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring GetTranslationUri(const 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 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;
|
if (!TKK) return FormatString(L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=%s&q=%s", translateTo->c_str(), text);
|
||||||
|
|
||||||
// Artikash 8/19/2018: reverse engineered from translate.google.com
|
// Artikash 8/19/2018: reverse engineered from translate.google.com
|
||||||
std::wstring escapedText;
|
std::wstring escapedText;
|
||||||
unsigned a = _time64(NULL) / 3600, b = a; // <- the first part of TKK
|
unsigned a = _time64(NULL) / 3600, b = a; // <- the first part of TKK
|
||||||
for (unsigned char ch : WideStringToString(text))
|
for (unsigned char ch : WideStringToString(text))
|
||||||
{
|
{
|
||||||
wchar_t escapedChar[4] = {};
|
escapedText += FormatString(L"%%%02X", (int)ch);
|
||||||
swprintf_s<4>(escapedChar, L"%%%02X", (int)ch);
|
|
||||||
escapedText += escapedChar;
|
|
||||||
a += ch;
|
a += ch;
|
||||||
a += a << 10;
|
a += a << 10;
|
||||||
a ^= a >> 6;
|
a ^= a >> 6;
|
||||||
@ -127,56 +94,28 @@ std::wstring GetTranslationUri(const std::wstring& text, unsigned TKK)
|
|||||||
a %= 1000000;
|
a %= 1000000;
|
||||||
b ^= a;
|
b ^= a;
|
||||||
|
|
||||||
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;
|
return FormatString(L"/translate_a/single?client=t&dt=ld&dt=rm&dt=t&tl=%s&tk=%u.%u&q=%s", translateTo->c_str(), a, b, escapedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (sentenceInfo["text number"] == 0) return false;
|
static unsigned TKK = 0;
|
||||||
|
if (!TKK)
|
||||||
|
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", L"/" })
|
||||||
|
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"(\\d{7,})'")))
|
||||||
|
_InterlockedCompareExchange(&TKK, stoll(results[1]), 0);
|
||||||
|
|
||||||
static std::atomic<HINTERNET> internet = NULL;
|
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", GetTranslationUri(text, TKK).c_str() })
|
||||||
if (!internet) internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
|
|
||||||
static std::atomic<unsigned> TKK = 0;
|
|
||||||
static RateLimiter rateLimiter(30, 60 * 1000);
|
|
||||||
|
|
||||||
std::wstring translation;
|
|
||||||
if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
|
|
||||||
else if (internet)
|
|
||||||
{
|
{
|
||||||
if (!TKK)
|
// Response formatted as JSON: starts with "[[[" and translation is enclosed in quotes followed by a comma
|
||||||
if (InternetHandle connection = WinHttpConnect(internet, L"translate.google.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
|
if (httpRequest.response[0] == L'[')
|
||||||
if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", L"/", NULL, NULL, NULL, WINHTTP_FLAG_SECURE))
|
{
|
||||||
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
|
std::wstring translation;
|
||||||
if (auto response = ReceiveHttpRequest(request))
|
for (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\\[\"(.*?)\",[n\"]")); httpRequest.response = results.suffix())
|
||||||
if (std::wsmatch results; std::regex_search(response.value(), results, std::wregex(L"(\\d{7,})'"))) TKK = stoll(results[1]);
|
translation += std::wstring(results[1]) + L" ";
|
||||||
|
if (!translation.empty()) return { true, translation };
|
||||||
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))
|
return { false, FormatString(L"%s (TKK=%u)", TRANSLATION_ERROR, _InterlockedExchange(&TKK, 0)) };
|
||||||
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
|
|
||||||
if (auto response = ReceiveHttpRequest(request))
|
|
||||||
// Response formatted as JSON: starts with [[["
|
|
||||||
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" ";
|
|
||||||
Unescape(translation);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
translation = TRANSLATION_ERROR + (L" (TKK=" + std::to_wstring(TKK) + L")");
|
|
||||||
TKK = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
if (translation.empty()) translation = TRANSLATION_ERROR;
|
|
||||||
sentence += L"\n" + translation;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(
|
|
||||||
{
|
|
||||||
std::wstring test = L"こんにちは";
|
|
||||||
ProcessSentence(test, { SentenceInfo::DUMMY });
|
|
||||||
assert(test.find(L"Hello") != std::wstring::npos);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
@ -113,7 +113,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
luaL_dostring(L, "ProcessSentence = nil");
|
luaL_dostring(L, "ProcessSentence = nil");
|
||||||
if (luaL_dostring(L, script->c_str()) != LUA_OK)
|
if (luaL_dostring(L, script->c_str()) != LUA_OK)
|
||||||
{
|
{
|
||||||
sentence += L"\n" + FormatWideString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)).c_str());
|
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
|
||||||
lua_settop(L, 0);
|
lua_settop(L, 0);
|
||||||
return logErrors;
|
return logErrors;
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
|
|
||||||
if (lua_getglobal(L, "ProcessSentence") != LUA_TFUNCTION)
|
if (lua_getglobal(L, "ProcessSentence") != LUA_TFUNCTION)
|
||||||
{
|
{
|
||||||
sentence += L"\n" + FormatWideString(LUA_ERROR, L"ProcessSentence is not a function");
|
sentence += L"\n" + FormatString(LUA_ERROR, L"ProcessSentence is not a function");
|
||||||
lua_settop(L, 0);
|
lua_settop(L, 0);
|
||||||
return logErrors;
|
return logErrors;
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
}
|
}
|
||||||
if (lua_pcallk(L, 2, 1, 0, NULL, NULL) != LUA_OK)
|
if (lua_pcallk(L, 2, 1, 0, NULL, NULL) != LUA_OK)
|
||||||
{
|
{
|
||||||
sentence += L"\n" + FormatWideString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)).c_str());
|
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
|
||||||
lua_settop(L, 0);
|
lua_settop(L, 0);
|
||||||
return logErrors;
|
return logErrors;
|
||||||
}
|
}
|
||||||
|
66
extensions/network.cpp
Normal file
66
extensions/network.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "network.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
HttpRequest::HttpRequest(
|
||||||
|
const wchar_t* agentName,
|
||||||
|
const wchar_t* serverName,
|
||||||
|
const wchar_t* action,
|
||||||
|
const wchar_t* objectName,
|
||||||
|
DWORD requestFlags,
|
||||||
|
const wchar_t* httpVersion,
|
||||||
|
const wchar_t* referrer,
|
||||||
|
const wchar_t** acceptTypes,
|
||||||
|
const wchar_t* headers,
|
||||||
|
void* body,
|
||||||
|
DWORD bodyLength
|
||||||
|
)
|
||||||
|
{
|
||||||
|
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_HTTPS_PORT, 0))
|
||||||
|
if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags))
|
||||||
|
if (WinHttpSendRequest(request, headers, -1UL, body, bodyLength, bodyLength, NULL))
|
||||||
|
{
|
||||||
|
WinHttpReceiveResponse(request, NULL);
|
||||||
|
std::string data;
|
||||||
|
DWORD availableSize, downloadedSize;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
availableSize = 0;
|
||||||
|
WinHttpQueryDataAvailable(request, &availableSize);
|
||||||
|
if (!availableSize) break;
|
||||||
|
std::vector<char> buffer(availableSize);
|
||||||
|
WinHttpReadData(request, buffer.data(), availableSize, &downloadedSize);
|
||||||
|
data.append(buffer.data(), downloadedSize);
|
||||||
|
} while (availableSize > 0);
|
||||||
|
response = StringToWideString(data);
|
||||||
|
this->connection = std::move(connection);
|
||||||
|
this->request = std::move(request);
|
||||||
|
}
|
||||||
|
else errorCode = GetLastError();
|
||||||
|
else errorCode = GetLastError();
|
||||||
|
else errorCode = GetLastError();
|
||||||
|
else errorCode = GetLastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring Escape(const std::wstring& text)
|
||||||
|
{
|
||||||
|
std::wstring escaped;
|
||||||
|
for (unsigned char ch : WideStringToString(text)) escaped += FormatString(L"%%%02X", (int)ch);
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unescape(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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +1,32 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "util.h"
|
#include "common.h"
|
||||||
#include <winhttp.h>
|
#include <winhttp.h>
|
||||||
|
|
||||||
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
||||||
|
|
||||||
inline std::optional<std::wstring> ReceiveHttpRequest(HINTERNET request)
|
struct HttpRequest
|
||||||
{
|
{
|
||||||
WinHttpReceiveResponse(request, NULL);
|
HttpRequest(
|
||||||
std::string data;
|
const wchar_t* agentName,
|
||||||
DWORD dwSize, dwDownloaded;
|
const wchar_t* serverName,
|
||||||
do
|
const wchar_t* action,
|
||||||
{
|
const wchar_t* objectName,
|
||||||
dwSize = 0;
|
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
|
||||||
WinHttpQueryDataAvailable(request, &dwSize);
|
const wchar_t* httpVersion = NULL,
|
||||||
if (!dwSize) break;
|
const wchar_t* referrer = NULL,
|
||||||
std::vector<char> buffer(dwSize);
|
const wchar_t** acceptTypes = NULL,
|
||||||
WinHttpReadData(request, buffer.data(), dwSize, &dwDownloaded);
|
const wchar_t* headers = NULL,
|
||||||
data.append(buffer.data(), dwDownloaded);
|
void* body = NULL,
|
||||||
} while (dwSize > 0);
|
DWORD bodyLength = 0
|
||||||
|
);
|
||||||
|
operator bool() { return errorCode == ERROR_SUCCESS; }
|
||||||
|
|
||||||
if (data.empty()) return {};
|
std::wstring response;
|
||||||
return StringToWideString(data);
|
InternetHandle connection = NULL;
|
||||||
}
|
InternetHandle request = NULL;
|
||||||
|
DWORD errorCode = ERROR_SUCCESS;
|
||||||
inline void Unescape(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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RateLimiter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RateLimiter(int tokenCount, int delay) : tokenCount(tokenCount), delay(delay) {}
|
|
||||||
|
|
||||||
bool Request()
|
|
||||||
{
|
|
||||||
auto tokens = this->tokens.Acquire();
|
|
||||||
tokens->push_back(GetTickCount());
|
|
||||||
if (tokens->size() > tokenCount * 5) tokens->erase(tokens->begin(), tokens->begin() + tokenCount * 3);
|
|
||||||
tokens->erase(std::remove_if(tokens->begin(), tokens->end(), [this](DWORD token) { return GetTickCount() - token > delay; }), tokens->end());
|
|
||||||
return tokens->size() < tokenCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int tokenCount, delay;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Synchronized<std::vector<DWORD>> tokens;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::wstring Escape(const std::wstring& text);
|
||||||
|
void Unescape(std::wstring& text);
|
||||||
|
@ -11,6 +11,7 @@ extern const char* THREAD_LINKER;
|
|||||||
extern const char* LINK;
|
extern const char* LINK;
|
||||||
extern const char* THREAD_LINK_FROM;
|
extern const char* THREAD_LINK_FROM;
|
||||||
extern const char* THREAD_LINK_TO;
|
extern const char* THREAD_LINK_TO;
|
||||||
|
extern const char* HEXADECIMAL;
|
||||||
|
|
||||||
std::mutex m;
|
std::mutex m;
|
||||||
std::unordered_map<int64_t, std::unordered_multiset<int64_t>> linkedTextHandles;
|
std::unordered_map<int64_t, std::unordered_multiset<int64_t>> linkedTextHandles;
|
||||||
@ -29,8 +30,8 @@ struct : QMainWindow
|
|||||||
connect(addLink, &QPushButton::clicked, [=]
|
connect(addLink, &QPushButton::clicked, [=]
|
||||||
{
|
{
|
||||||
bool ok1, ok2, ok3, ok4;
|
bool ok1, ok2, ok3, ok4;
|
||||||
int from = QInputDialog::getText(this, THREAD_LINK_FROM, "", QLineEdit::Normal, "0x", &ok1, Qt::WindowCloseButtonHint).toInt(&ok2, 16);
|
int from = QInputDialog::getText(this, THREAD_LINK_FROM, HEXADECIMAL, QLineEdit::Normal, "", &ok1, Qt::WindowCloseButtonHint).toInt(&ok2, 16);
|
||||||
int to = QInputDialog::getText(this, THREAD_LINK_TO, "", QLineEdit::Normal, "0x", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
|
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
|
||||||
if (ok1 && ok2 && ok3 && ok4)
|
if (ok1 && ok2 && ok3 && ok4)
|
||||||
{
|
{
|
||||||
std::lock_guard l(m);
|
std::lock_guard l(m);
|
||||||
|
86
extensions/translatewrapper.cpp
Normal file
86
extensions/translatewrapper.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include "extension.h"
|
||||||
|
#include "network.h"
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QInputDialog>
|
||||||
|
|
||||||
|
extern const char* SELECT_LANGUAGE;
|
||||||
|
extern const char* SELECT_LANGUAGE_MESSAGE;
|
||||||
|
extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
|
||||||
|
|
||||||
|
extern const char* TRANSLATION_PROVIDER;
|
||||||
|
extern QStringList languages;
|
||||||
|
extern Synchronized<std::wstring> translateTo;
|
||||||
|
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
||||||
|
|
||||||
|
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||||
|
{
|
||||||
|
switch (ul_reason_for_call)
|
||||||
|
{
|
||||||
|
case DLL_PROCESS_ATTACH:
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, []
|
||||||
|
{
|
||||||
|
translateTo->assign(QInputDialog::getItem(
|
||||||
|
nullptr,
|
||||||
|
SELECT_LANGUAGE,
|
||||||
|
QString(SELECT_LANGUAGE_MESSAGE).arg(TRANSLATION_PROVIDER),
|
||||||
|
languages,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
nullptr,
|
||||||
|
Qt::WindowCloseButtonHint)
|
||||||
|
.split(": ")[1]
|
||||||
|
.toStdWString()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DLL_PROCESS_DETACH:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||||
|
{
|
||||||
|
if (sentenceInfo["text number"] == 0) return false;
|
||||||
|
|
||||||
|
static class
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool Request()
|
||||||
|
{
|
||||||
|
auto tokens = this->tokens.Acquire();
|
||||||
|
tokens->push_back(GetTickCount());
|
||||||
|
if (tokens->size() > tokenCount * 5) tokens->erase(tokens->begin(), tokens->begin() + tokenCount * 3);
|
||||||
|
tokens->erase(std::remove_if(tokens->begin(), tokens->end(), [this](DWORD token) { return GetTickCount() - token > delay; }), tokens->end());
|
||||||
|
return tokens->size() < tokenCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int tokenCount = 30, delay = 60 * 1000;
|
||||||
|
Synchronized<std::vector<DWORD>> tokens;
|
||||||
|
} rateLimiter;
|
||||||
|
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
|
||||||
|
|
||||||
|
bool cache = false;
|
||||||
|
std::wstring translation;
|
||||||
|
if (translationCache->count(sentence) != 0) translation = translationCache->at(sentence);
|
||||||
|
else if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
|
||||||
|
else std::tie(cache, translation) = Translate(sentence);
|
||||||
|
if (cache) translationCache->insert({ sentence, translation });
|
||||||
|
Unescape(translation);
|
||||||
|
|
||||||
|
sentence += L"\n" + translation;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(
|
||||||
|
{
|
||||||
|
std::wstring test = L"こんにちは";
|
||||||
|
ProcessSentence(test, { SentenceInfo::DUMMY });
|
||||||
|
assert(test.find(L"Hello") != std::wstring::npos);
|
||||||
|
}
|
||||||
|
);
|
@ -81,33 +81,39 @@ static struct
|
|||||||
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||||
} DUMMY;
|
} DUMMY;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline auto FormatArg(T arg) { return arg; }
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4996)
|
#pragma warning(disable: 4996)
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline std::string FormatString(const char* format, Args... args)
|
inline std::string FormatString(const char* format, Args... args)
|
||||||
{
|
{
|
||||||
std::string buffer(snprintf(nullptr, 0, format, args...), '\0');
|
std::string buffer(snprintf(nullptr, 0, format, FormatArg(args)...), '\0');
|
||||||
sprintf(buffer.data(), format, args...);
|
sprintf(buffer.data(), format, FormatArg(args)...);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline std::wstring FormatWideString(const wchar_t* format, Args... args)
|
inline std::wstring FormatString(const wchar_t* format, Args... args)
|
||||||
{
|
{
|
||||||
std::wstring buffer(_snwprintf(nullptr, 0, format, args...), L'\0');
|
std::wstring buffer(_snwprintf(nullptr, 0, format, FormatArg(args)...), L'\0');
|
||||||
_swprintf(buffer.data(), format, args...);
|
_swprintf(buffer.data(), format, FormatArg(args)...);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
#pragma warning(pop)
|
#pragma warning(pop)
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#define TEST(...) inline auto TEST_RUNNER_DUMMY = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
||||||
#else
|
#else
|
||||||
#define TEST(...)
|
#define TEST(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#define TEST_SYNC(...) inline auto TEST_RUNNER_DUMMY = [] { __VA_ARGS__; return 0UL; }();
|
#define TEST_SYNC(...) static auto _ = [] { __VA_ARGS__; return 0UL; }();
|
||||||
#else
|
#else
|
||||||
#define TEST_SYNC(...)
|
#define TEST_SYNC(...)
|
||||||
#endif
|
#endif
|
||||||
|
18
text.cpp
18
text.cpp
@ -103,8 +103,7 @@ const char* READ_ERROR = u8"Textractor: Reader ERROR (likely an incorrect R-code
|
|||||||
const char* HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
const char* HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
||||||
const char* COULD_NOT_FIND = u8"Textractor: could not find text";
|
const char* COULD_NOT_FIND = u8"Textractor: could not find text";
|
||||||
const char* SELECT_LANGUAGE = u8"Select language";
|
const char* SELECT_LANGUAGE = u8"Select language";
|
||||||
const char* BING_PROMPT = u8"What language should Bing translate to?";
|
const char* SELECT_LANGUAGE_MESSAGE = u8"What language should %1 translate to?";
|
||||||
const char* GOOGLE_PROMPT = u8"What language should Google translate to?";
|
|
||||||
const wchar_t* TOO_MANY_TRANS_REQUESTS = L"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 wchar_t* TRANSLATION_ERROR = L"Error while translating";
|
||||||
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
|
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
|
||||||
@ -149,8 +148,9 @@ const char* THREAD_LINKER = u8"Thread Linker";
|
|||||||
const char* LINK = u8"Link";
|
const char* LINK = u8"Link";
|
||||||
const char* THREAD_LINK_FROM = u8"Thread number to link from";
|
const char* THREAD_LINK_FROM = u8"Thread number to link from";
|
||||||
const char* THREAD_LINK_TO = u8"Thread number to link to";
|
const char* THREAD_LINK_TO = u8"Thread number to link to";
|
||||||
|
const char* HEXADECIMAL = u8"Hexadecimal";
|
||||||
|
|
||||||
inline auto _ = []
|
static auto _ = []
|
||||||
{
|
{
|
||||||
#ifdef TURKISH
|
#ifdef TURKISH
|
||||||
ATTACH = u8"Oyuna bağla";
|
ATTACH = u8"Oyuna bağla";
|
||||||
@ -236,8 +236,7 @@ Estoy buscando un nuevo trabajo: por favor envíame un correo si estás contrata
|
|||||||
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
||||||
COULD_NOT_FIND = u8"Textractor: no se puede encontrar texto";
|
COULD_NOT_FIND = u8"Textractor: no se puede encontrar texto";
|
||||||
SELECT_LANGUAGE = u8"Seleccionar lenguaje";
|
SELECT_LANGUAGE = u8"Seleccionar lenguaje";
|
||||||
BING_PROMPT = u8"¿A qué idioma debe traducir Bing?";
|
SELECT_LANGUAGE_MESSAGE = u8"¿A qué idioma debe traducir %1?";
|
||||||
GOOGLE_PROMPT = u8"¿A qué idioma debe traducir Google?";
|
|
||||||
TOO_MANY_TRANS_REQUESTS = L"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";
|
TRANSLATION_ERROR = L"Error al traducir";
|
||||||
EXTRA_WINDOW_INFO = u8R"(Clic derecho para configurar
|
EXTRA_WINDOW_INFO = u8R"(Clic derecho para configurar
|
||||||
@ -303,8 +302,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
|
|||||||
HIJACK_ERROR = u8"Textractor: Hijack 错误";
|
HIJACK_ERROR = u8"Textractor: Hijack 错误";
|
||||||
COULD_NOT_FIND = u8"Textractor: 无法找到文本";
|
COULD_NOT_FIND = u8"Textractor: 无法找到文本";
|
||||||
SELECT_LANGUAGE = u8"选择语言";
|
SELECT_LANGUAGE = u8"选择语言";
|
||||||
BING_PROMPT = u8"想要使用 Bing 翻译到哪种语言?";
|
SELECT_LANGUAGE_MESSAGE = u8"想要使用 %1 翻译到哪种语言?";
|
||||||
GOOGLE_PROMPT = u8"想要使用 Google 翻译到哪种语言?";
|
|
||||||
TOO_MANY_TRANS_REQUESTS = L"太多翻译请求: 拒绝生成更多";
|
TOO_MANY_TRANS_REQUESTS = L"太多翻译请求: 拒绝生成更多";
|
||||||
TRANSLATION_ERROR = L"翻译时出错";
|
TRANSLATION_ERROR = L"翻译时出错";
|
||||||
EXTRA_WINDOW_INFO = u8R"(右键修改设置
|
EXTRA_WINDOW_INFO = u8R"(右键修改设置
|
||||||
@ -373,8 +371,7 @@ I'm currently looking for a new job: email me if you know anyone hiring US softw
|
|||||||
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
||||||
COULD_NOT_FIND = u8"Textractor: невозможно найти текст";
|
COULD_NOT_FIND = u8"Textractor: невозможно найти текст";
|
||||||
SELECT_LANGUAGE = u8"Выберете язык";
|
SELECT_LANGUAGE = u8"Выберете язык";
|
||||||
BING_PROMPT = u8"На какой язык переводить в Bing?";
|
SELECT_LANGUAGE_MESSAGE = u8"На какой язык переводить в %1?";
|
||||||
GOOGLE_PROMPT = u8"На какой язык переводить в Google?";
|
|
||||||
TOO_MANY_TRANS_REQUESTS = L"Слишком много запросов для перевода: отклонено";
|
TOO_MANY_TRANS_REQUESTS = L"Слишком много запросов для перевода: отклонено";
|
||||||
TRANSLATION_ERROR = L"Ошибка при переводе";
|
TRANSLATION_ERROR = L"Ошибка при переводе";
|
||||||
EXTRA_WINDOW_INFO = u8R"(Правый клик для изменения настроек
|
EXTRA_WINDOW_INFO = u8R"(Правый клик для изменения настроек
|
||||||
@ -473,8 +470,7 @@ Jika kamu menyukai project ini, tolong sebarluaskan project ini :))";
|
|||||||
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
HIJACK_ERROR = u8"Textractor: Hijack ERROR";
|
||||||
COULD_NOT_FIND = u8"Textractor: tidak dapat menemukan teks";
|
COULD_NOT_FIND = u8"Textractor: tidak dapat menemukan teks";
|
||||||
SELECT_LANGUAGE = u8"Pilih bahasa";
|
SELECT_LANGUAGE = u8"Pilih bahasa";
|
||||||
BING_PROMPT = u8"Bahasa apakah yang Bing harus terjemahkan?";
|
SELECT_LANGUAGE_MESSAGE = u8"Bahasa apakah yang %1 harus terjemahkan?";
|
||||||
GOOGLE_PROMPT = u8"Bahasa apakah yang Google harus terjemahkan?";
|
|
||||||
TOO_MANY_TRANS_REQUESTS = L"Terlalu banyak permintaan terjemahan: menolak untuk menerjemahkan";
|
TOO_MANY_TRANS_REQUESTS = L"Terlalu banyak permintaan terjemahan: menolak untuk menerjemahkan";
|
||||||
TRANSLATION_ERROR = L"Terjadi kesalahan ketika menerjemahkan";
|
TRANSLATION_ERROR = L"Terjadi kesalahan ketika menerjemahkan";
|
||||||
EXTRA_WINDOW_INFO = u8R"(Klik kanan untuk merubah pengaturan
|
EXTRA_WINDOW_INFO = u8R"(Klik kanan untuk merubah pengaturan
|
||||||
|
Loading…
x
Reference in New Issue
Block a user