diff --git a/build.gradle.kts b/build.gradle.kts index 78b9ef1..af67d0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,9 +23,6 @@ dependencies { val fatJar = task("fatJar", type = Jar::class) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE archiveFileName.set("${project.name.toLowerCaseAsciiOnly()}.jar") - manifest { - attributes["Implementation-Title"] = "Gradle Jar File Example" - } from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) with(tasks.jar.get() as CopySpec) } diff --git a/src/main/kotlin/dev/jombi/copytotrans/Main.kt b/src/main/kotlin/dev/jombi/copytotrans/Main.kt index 957b561..ce5f532 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/Main.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/Main.kt @@ -52,7 +52,7 @@ fun main() { GlobalScreen.unregisterNativeHook() exitProcess(0) } - val keybindings = arrayOf(google/*, googleOld*/, papago, exitKey) + val keybindings = arrayOf(google, papago, exitKey) GlobalScreen.registerNativeHook() GlobalScreen.addNativeKeyListener(object : NativeKeyListener { override fun nativeKeyPressed(e: NativeKeyEvent) { diff --git a/src/main/kotlin/dev/jombi/copytotrans/TranslateStatus.kt b/src/main/kotlin/dev/jombi/copytotrans/TranslateStatus.kt index 642e3f1..afa3bd8 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/TranslateStatus.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/TranslateStatus.kt @@ -47,10 +47,8 @@ class TranslateStatus : JDialog() { try { val screenSize = Toolkit.getDefaultToolkit().screenSize setLocation(screenSize.width / 2 - lab.width, screenSize.height / 2 + 32) -// showOverlay() pack() } catch (e: AWTException) { -// error("Error while showing overlay", e) exitProcess(-1) } } diff --git a/src/main/kotlin/dev/jombi/copytotrans/Translators.kt b/src/main/kotlin/dev/jombi/copytotrans/Translators.kt index 30322a3..780488e 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/Translators.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/Translators.kt @@ -2,11 +2,10 @@ package dev.jombi.copytotrans import com.fasterxml.jackson.databind.ObjectMapper import dev.jombi.copytotrans.translator.Translator -import dev.jombi.copytotrans.translator.impl.GoogleAnonTranslate -import dev.jombi.copytotrans.translator.impl.PapagoTranslate import dev.jombi.copytotrans.translator.impl.newg.GoogleRPCTranslate -import java.net.HttpURLConnection -import java.net.URL +import dev.jombi.copytotrans.translator.impl.newp.PapagoAnonTranslate +import dev.jombi.copytotrans.translator.impl.old.GoogleAnonTranslate +import dev.jombi.copytotrans.translator.impl.old.PapagoTokenedTranslate import java.net.URLEncoder interface Translators { @@ -19,79 +18,13 @@ interface Translators { object GoogleOld : Translators { override val translator = GoogleAnonTranslate() } + object PapagoOld : Translators { + override val translator = PapagoTokenedTranslate() + } object Papago : Translators { - override val translator = PapagoTranslate() + override val translator = PapagoAnonTranslate() } } fun ObjectMapper.buildJson(vararg pairs: Pair): String = writeValueAsString(mapOf(*pairs)) -fun buildUrlEncoded(vararg pairs: Pair>) = pairs.joinToString("&") { "${URLEncoder.encode(it.first, "utf-8")}=${URLEncoder.encode("${it.second}", "utf-8")}" } -/* -export class Translator { - protected options: typeof defaults & TranslateOptions; - - constructor(protected inputText: string, options?: TranslateOptions) { - this.options = Object.assign({}, defaults, options); - } - - async translate() { - const url = this.buildUrl(); - const fetchOptions = this.buildFetchOptions(); - const res = await fetch(url, fetchOptions); - if (!res.ok) throw await this.buildError(res); - const raw = await res.json() as RawResponse; - const text = this.buildResText(raw); - return { text, raw }; - } - - protected buildUrl() { - const { host } = this.options; - return [ - `https://${host}/translate_a/single`, - '?client=at', - '&dt=t', // return sentences - '&dt=rm', // add translit to sentences - '&dj=1', // result as pretty json instead of deep nested arrays - ].join(''); - } - - protected buildBody() { - const { from, to } = this.options; - const params = { - sl: from, - tl: to, - q: this.inputText, - }; - return new URLSearchParams(params).toString(); - } - - protected buildFetchOptions() { - const { fetchOptions } = this.options; - const res = Object.assign({}, fetchOptions); - res.method = 'POST'; - res.headers = Object.assign({}, res.headers, { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }); - res.body = this.buildBody(); - return res; - } - - protected buildResText({ sentences }: RawResponse) { - return sentences - .filter((s): s is Sentence => 'trans' in s) - .map(s => s.trans) - .join(''); - } - - protected async buildError(res: Response) { - if (res.status === 429) { - const text = await res.text(); - const { ip, time, url } = extractTooManyRequestsInfo(text); - const message = `${res.statusText} IP: ${ip}, Time: ${time}, Url: ${url}`; - return createHttpError(res.status, message); - } else { - return createHttpError(res.status, res.statusText); - } - } -} -*/ \ No newline at end of file +fun buildUrlEncoded(vararg pairs: Pair>) = pairs.joinToString("&") { "${it.first}=${URLEncoder.encode("${it.second}", "utf-8")}" } \ No newline at end of file diff --git a/src/main/kotlin/dev/jombi/copytotrans/config/Config.kt b/src/main/kotlin/dev/jombi/copytotrans/config/Config.kt index cd466ed..e5ea4d5 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/config/Config.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/config/Config.kt @@ -14,15 +14,15 @@ fun makeDefaultConfig() { val cfg = getRawConfig() val path = Path("config.json") val p = Property() - if (cfg.hasNonNull("papagoKey")) p.papagoKey = cfg["papagoKey"].asText() - if (cfg.hasNonNull("papagoSecret")) p.papagoSecret = cfg["papagoSecret"].asText() +// if (cfg.hasNonNull("papagoKey")) p.papagoKey = cfg["papagoKey"].asText() +// if (cfg.hasNonNull("papagoSecret")) p.papagoSecret = cfg["papagoSecret"].asText() if (cfg.hasNonNull("sound")) p.sound = cfg["sound"].asBoolean() if (cfg.hasNonNull("overlay")) p.overlay = cfg["overlay"].asBoolean() mapper.writerWithDefaultPrettyPrinter().writeValue(path.outputStream(), p) } -fun getPapagoApiKey(): String = getConfig().papagoKey -fun getPapagoApiSecret(): String = getConfig().papagoSecret +//fun getPapagoApiKey(): String = getConfig().papagoKey +//fun getPapagoApiSecret(): String = getConfig().papagoSecret fun shouldShowOverlay(): Boolean = getConfig().overlay fun shouldPlaySound(): Boolean = getConfig().sound \ No newline at end of file diff --git a/src/main/kotlin/dev/jombi/copytotrans/config/Property.kt b/src/main/kotlin/dev/jombi/copytotrans/config/Property.kt index 574c945..9704247 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/config/Property.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/config/Property.kt @@ -1,8 +1,8 @@ package dev.jombi.copytotrans.config data class Property( - var papagoKey: String = "", - var papagoSecret: String = "", +// var papagoKey: String = "", +// var papagoSecret: String = "", var overlay: Boolean = true, var sound: Boolean = true ) \ No newline at end of file diff --git a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoAnonTranslate.kt b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoAnonTranslate.kt new file mode 100644 index 0000000..0c4f066 --- /dev/null +++ b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoAnonTranslate.kt @@ -0,0 +1,147 @@ +package dev.jombi.copytotrans.translator.impl.newp + +import dev.jombi.copytotrans.buildUrlEncoded +import dev.jombi.copytotrans.config.mapper +import dev.jombi.copytotrans.translator.Translator +import dev.jombi.copytotrans.translator.impl.newg.FailedToTranslateException +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLEncoder +import java.security.SecureRandom +import java.security.cert.X509Certificate +import java.util.* +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager +import kotlin.random.Random + +class PapagoAnonTranslate : Translator { + private val endpoint = "https://papago.naver.com" + val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0" + val headers = hashMapOf() + private val TIMESTAMP_REGEX = Regex("var query= null,timestamp= ([0-9]+)") + private val timestampFromHtml: Long + private val delayedTimestamp: Long + private val trustAllCerts: Array = arrayOf(object : X509TrustManager { + override fun checkClientTrusted(p0: Array?, p1: String?) { + } + + override fun checkServerTrusted(p0: Array?, p1: String?) { + } + + override fun getAcceptedIssuers(): Array? { + return null + } + + }) + + init { + val sc = SSLContext.getInstance("SSL") + sc.init(null, trustAllCerts, SecureRandom()) + HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory) + val con = URL(endpoint).openConnection() as HttpURLConnection + con.requestMethod = "GET" + con.addRequestProperty("User-Agent", userAgent) + con.connect() + val (jsess, value) = con.getHeaderField("set-cookie").split(';')[0].split('=') + headers[jsess] = value + headers["papago_skin_locale"] = "en" + delayedTimestamp = System.currentTimeMillis() + Random.nextInt(200, 800) + timestampFromHtml = + TIMESTAMP_REGEX.find(String(con.inputStream.readBytes()))?.groupValues?.get(1)?.toLong() ?: delayedTimestamp + +// println(timestampFromHtml) +// println(delayedTimestamp) +// println(delayedTimestamp - timestampFromHtml) + PapagoUUIDGen.gen() + } + + override fun translate(target: String): String { + val toTranslate = detect(target) + val offset = System.currentTimeMillis() + timestampFromHtml - delayedTimestamp // (new Date).getTime() + a - d + val url = "$endpoint/apis/n2mt/translate" + val uuid = PapagoUUIDGen.gen() + val hash = createAuthorization(offset, url) + val transHeader = arrayOf( + "Accept" to "application/json", + "Accept-Language" to "en", + "Authorization" to "PPG $uuid:$hash", + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "Timestamp" to "$offset", + "device-type" to "pc", + "x-apigw-partnerid" to "papago", + "Cookie" to headers.toList().joinToString("; ") { "${it.first}=${URLEncoder.encode(it.second, "utf-8")}" }, + "User-Agent" to userAgent, + ) + val body = buildUrlEncoded( + "deviceId" to uuid, + "locale" to "en", + "dict" to "false", + "honorific" to "false", + "instant" to "false", + "paging" to "false", + "source" to toTranslate, + "target" to "ko", + "text" to target + ) + val con = URL(url).openConnection() as HttpURLConnection + for ((key, value) in transHeader) con.setRequestProperty(key, value) + con.requestMethod = "POST" + con.doOutput = true + con.outputStream.use { + it.write(body.toByteArray(charset("utf-8"))) + it.flush() + } + con.connect() + if (con.responseCode in 200..299) { +// return String(con.inputStream.readBytes()) + return mapper.readTree(con.inputStream)["translatedText"].asText() + } else { + println(String(con.errorStream.readBytes())) + throw FailedToTranslateException(con.responseCode) + } + } + + fun detect(string: String): String { + val offset = System.currentTimeMillis() + timestampFromHtml - delayedTimestamp // (new Date).getTime() + a - d + val url = "$endpoint/apis/langs/dect" + val uuid = PapagoUUIDGen.gen() + val hash = createAuthorization(offset, url) + val detectHeader = mapOf( + "Accept" to "application/json", + "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", + "device-type" to "pc", + "Cookie" to headers.toList().joinToString("; ") { "${it.first}=${URLEncoder.encode(it.second, "utf-8")}" }, + "User-Agent" to userAgent, + "Timestamp" to "$offset", + "Authorization" to "PPG $uuid:$hash" + ) + val con = URL(url).openConnection() as HttpURLConnection + for ((key, value) in detectHeader) con.setRequestProperty(key, value) + con.requestMethod = "POST" + con.doOutput = true + val body = buildUrlEncoded("query" to string) + con.outputStream.use { + it.write(body.toByteArray(charset("utf-8"))) + it.flush() + } + con.connect() + if (con.responseCode in 200..299) { + return mapper.readTree(con.inputStream)["langCode"].asText() + } else { + println(String(con.errorStream.readBytes())) + throw FailedToTranslateException(con.responseCode) + } + } + + private fun createAuthorization(time: Long, url: String): String { + val uuid = PapagoUUIDGen.gen() + val mac = Mac.getInstance("HmacMD5") + mac.init(SecretKeySpec("v1.7.3_de60216eaa".toByteArray(charset("utf-8")), "HmacMD5")) + val myMac = uuid + "\n" + url.split("?")[0] + "\n" + time + return Base64.getEncoder().encodeToString(mac.doFinal(myMac.toByteArray())) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoUUIDGen.kt b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoUUIDGen.kt new file mode 100644 index 0000000..a8b62b0 --- /dev/null +++ b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/newp/PapagoUUIDGen.kt @@ -0,0 +1,19 @@ +package dev.jombi.copytotrans.translator.impl.newp + +object PapagoUUIDGen { + private var stored: String? = null + fun gen(): String { + if (stored != null) return stored!! + var a = System.currentTimeMillis() + val genned = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".mapIndexed { i, it -> + if (it in "xy") { + val rng = Math.random() + val thing = ((a + 16 * rng) % 16).toInt() + a /= 16 + (if ('x' == it) thing else 3 and thing or 8).toString(16) + } else it + }.joinToString("") + stored = genned + return stored!! + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/GoogleAnonTranslate.kt b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/GoogleAnonTranslate.kt similarity index 97% rename from src/main/kotlin/dev/jombi/copytotrans/translator/impl/GoogleAnonTranslate.kt rename to src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/GoogleAnonTranslate.kt index 50cf009..44e1463 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/GoogleAnonTranslate.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/GoogleAnonTranslate.kt @@ -1,4 +1,4 @@ -package dev.jombi.copytotrans.translator.impl +package dev.jombi.copytotrans.translator.impl.old import dev.jombi.copytotrans.buildUrlEncoded import dev.jombi.copytotrans.config.mapper diff --git a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/PapagoTranslate.kt b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/PapagoTokenedTranslate.kt similarity index 75% rename from src/main/kotlin/dev/jombi/copytotrans/translator/impl/PapagoTranslate.kt rename to src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/PapagoTokenedTranslate.kt index 4cac7b0..d37d3c8 100644 --- a/src/main/kotlin/dev/jombi/copytotrans/translator/impl/PapagoTranslate.kt +++ b/src/main/kotlin/dev/jombi/copytotrans/translator/impl/old/PapagoTokenedTranslate.kt @@ -1,14 +1,12 @@ -package dev.jombi.copytotrans.translator.impl +package dev.jombi.copytotrans.translator.impl.old import dev.jombi.copytotrans.buildUrlEncoded -import dev.jombi.copytotrans.config.getPapagoApiKey -import dev.jombi.copytotrans.config.getPapagoApiSecret import dev.jombi.copytotrans.config.mapper import dev.jombi.copytotrans.translator.Translator import java.net.HttpURLConnection import java.net.URL -class PapagoTranslate : Translator { +class PapagoTokenedTranslate : Translator { val from = "auto" val to = "ko" override fun translate(target: String): String { @@ -16,8 +14,8 @@ class PapagoTranslate : Translator { con.doOutput = true con.doInput = true con.requestMethod = "POST" - con.addRequestProperty("X-Naver-Client-Id", getPapagoApiKey()) - con.addRequestProperty("X-Naver-Client-Secret", getPapagoApiSecret()) +// con.addRequestProperty("X-Naver-Client-Id", getPapagoApiKey()) +// con.addRequestProperty("X-Naver-Client-Secret", getPapagoApiSecret()) val body = buildUrlEncoded("source" to from, "target" to to, "text" to target) con.outputStream.use { it.write(body.toByteArray()) diff --git a/src/test/kotlin/PapagoAnonTest.kt b/src/test/kotlin/PapagoAnonTest.kt new file mode 100644 index 0000000..610026a --- /dev/null +++ b/src/test/kotlin/PapagoAnonTest.kt @@ -0,0 +1,8 @@ +package dev.jombi.copytotrans + +import dev.jombi.copytotrans.translator.impl.newp.PapagoAnonTranslate + +fun main() { + val anon = PapagoAnonTranslate() + println(anon.translate("hello")) +} \ No newline at end of file diff --git a/src/test/kotlin/UUIDTest.kt b/src/test/kotlin/UUIDTest.kt new file mode 100644 index 0000000..2170ddd --- /dev/null +++ b/src/test/kotlin/UUIDTest.kt @@ -0,0 +1,7 @@ +package dev.jombi.copytotrans + +import dev.jombi.copytotrans.translator.impl.newp.PapagoUUIDGen + +fun main() { + println(PapagoUUIDGen.gen()) +} \ No newline at end of file