diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/DeepL.kt b/app/src/main/java/com/bnyro/translate/api/deepl/DeepL.kt index 7ffbc0865..40961c594 100644 --- a/app/src/main/java/com/bnyro/translate/api/deepl/DeepL.kt +++ b/app/src/main/java/com/bnyro/translate/api/deepl/DeepL.kt @@ -19,8 +19,11 @@ package com.bnyro.translate.api.deepl import com.bnyro.translate.api.deepl.obj.DeeplLanguage import com.bnyro.translate.api.deepl.obj.DeeplTranslationResponse +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationResponse +import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header +import retrofit2.http.Headers import retrofit2.http.POST import retrofit2.http.Query @@ -37,4 +40,22 @@ interface DeepL { @Query("target_lang") target: String, @Query("text") query: String ): DeeplTranslationResponse + + @Headers( + "Accept: */*", + "Accept-Language: en-US,en;q=0.5", + "Authorization: None", + "Content-Type: application/json; charset=utf-8", + "Host: www2.deepl.com", + "Origin: chrome-extension://cofdbpoegempjloogbagkncekinflcnj", + "referer: https://www.deepl.com/", + "Sec-Fetch-Dest: empty", + "Sec-Fetch-Mode: cors", + "Sec-Fetch-Site: none", + "User-Agent: DeepLBrowserExtension/${DeeplEngine.WEB_CHROME_EXTENSION_VER} ${DeeplEngine.WEB_CHROME_USER_AGENT}" + ) + @POST("jsonrpc?client=chrome-extension,${DeeplEngine.WEB_CHROME_EXTENSION_VER}") + suspend fun webTranslate( + @Body body: String + ): DeeplWebTranslationResponse } diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt b/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt index cb2191005..5accaa00a 100644 --- a/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt +++ b/app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt @@ -17,22 +17,38 @@ package com.bnyro.translate.api.deepl +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationRequest +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationRequestParams +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationRequestParamsText +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationRequestParamsLang +import com.bnyro.translate.api.deepl.obj.DeeplWebTranslationRequestParamsLangPreference import com.bnyro.translate.const.ApiKeyState import com.bnyro.translate.db.obj.Language import com.bnyro.translate.obj.Translation import com.bnyro.translate.util.Preferences import com.bnyro.translate.util.RetrofitHelper import com.bnyro.translate.util.TranslationEngine +import kotlin.math.floor +import kotlin.math.roundToInt +import kotlinx.serialization.json.Json +import kotlinx.serialization.encodeToString +/** + * DeepL translator + * + * Uses the API if an API key is provided + * Fallbacks to emulating a browser (less reliable method) otherwise + */ class DeeplEngine : TranslationEngine( name = "DeepL", defaultUrl = "https://api-free.deepl.com", urlModifiable = false, - apiKeyState = ApiKeyState.REQUIRED, + apiKeyState = ApiKeyState.OPTIONAL, autoLanguageCode = "" ) { val useFreeApiKey = this.name + "selectedApiVersion" private val nonFreeUrl = "https://api.deepl.com" + private val webUrl = "https://www2.deepl.com" private lateinit var api: DeepL override fun createOrRecreate(): TranslationEngine = apply { api = RetrofitHelper.createApi(this) @@ -40,36 +56,117 @@ class DeeplEngine : TranslationEngine( private val apiKeyString = "DeepL-Auth-Key " + getApiKey() - override suspend fun getLanguages(): List = api.getLanguages( - apiKeyString - ).map { - Language( - code = it.language.lowercase(), - name = it.name - ) - }.toMutableList().apply { - add( - 0, - Language( - "", - "Auto" + override suspend fun getLanguages(): List = listOf(Language("", "Auto")) + + if (getApiKey().isNotEmpty()) { + api.getLanguages( + apiKeyString + ).map { + Language( + code = it.language.lowercase(), + name = it.name + ) + } + } else { + listOf( + // List is on https://www.deepl.com/translator + // Language code can be checked in URL after making a first translation + Language("ar", "Arabic"), + Language("bg", "Bulgarian"), + Language("zh", "Chinese"), + Language("cs", "Czech"), + Language("da", "Danish"), + Language("nl", "Dutch"), + Language("en", "English"), + Language("et", "Estonian"), + Language("fi", "Finnish"), + Language("fr", "French"), + Language("de", "German"), + Language("el", "Greek"), + Language("hu", "Hungarian"), + Language("id", "Indonesian"), + Language("it", "Italian"), + Language("ja", "Japanese"), + Language("ko", "Korean"), + Language("lv", "Latvian"), + Language("lt", "Lithuanian"), + Language("nb", "Norwegian (bokmål)"), + Language("pl", "Polish"), + Language("pt", "Portuguese"), + Language("ro", "Romanian"), + Language("ru", "Russian"), + Language("sk", "Slovak"), + Language("sl", "Slovenian"), + Language("es", "Spanish"), + Language("sv", "Swedish"), + Language("tr", "Turkish"), + Language("uk", "Ukrainian") ) - ) - } + } override suspend fun translate(query: String, source: String, target: String): Translation { - val response = api.translate( - apiKeyString, - sourceOrAuto(source.uppercase()), - target.uppercase(), - query - ) + if (getApiKey().isNotEmpty()) { + val response = api.translate( + apiKeyString, + sourceOrAuto(source.uppercase()), + target.uppercase(), + query + ) + + return Translation( + translatedText = response.translations.first().text, + detectedLanguage = response.translations.first().detectedSourceLanguage + ) + } else { + val id = (floor(Math.random().times(99999)) + 100000).roundToInt().times(1000) + val body = Json.encodeToString( + DeeplWebTranslationRequest( + jsonrpc = "2.0", + method = "LMT_handle_texts", + params = DeeplWebTranslationRequestParams( + texts = listOf( + DeeplWebTranslationRequestParamsText( + text = query, + ) + ), + splitting = "newlines", + lang = DeeplWebTranslationRequestParamsLang( + targetLang = target.uppercase(), + sourceLangUserSelected = sourceOrAuto(source.uppercase()).ifEmpty { "auto" }, + preference = DeeplWebTranslationRequestParamsLangPreference( + weight = emptyMap() + ) + ), + commonJobParams = emptyMap(), + timestamp = System.currentTimeMillis() + ), + id = id + ) + ).replace( + "\"method\":\"", + // The random ID determines the spacing to use, do NOT change it + // This is how the client side of the web service works and the server-side + // expects the same, otherwise you will get soft-banned + if ((id + 3) % 13 == 0 || (id + 5) % 29 == 0) { + "\"method\" : \"" + } else "\"method\": \"" + ) + val webResponse = api.webTranslate(body) - return Translation( - translatedText = response.translations.first().text, - detectedLanguage = response.translations.first().detected_source_language - ) + return Translation( + translatedText = webResponse.result.texts.firstOrNull()?.text ?: "", + detectedLanguage = webResponse.result.lang + ) + } } - override fun getUrl() = if (Preferences.get(useFreeApiKey, true)) defaultUrl else nonFreeUrl + override fun getUrl() = if (getApiKey().isNotEmpty()) { + if (Preferences.get(useFreeApiKey, true)) { + defaultUrl + } else nonFreeUrl + } else webUrl + + companion object { + const val WEB_CHROME_EXTENSION_VER = "1.18.0" + const val WEB_CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" + } } diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplTranslation.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplTranslation.kt index e6186466f..47efb2a8a 100644 --- a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplTranslation.kt +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplTranslation.kt @@ -17,10 +17,11 @@ package com.bnyro.translate.api.deepl.obj +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class DeeplTranslation( - val detected_source_language: String = "", + @SerialName("detected_source_language") val detectedSourceLanguage: String = "", val text: String = "" ) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequest.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequest.kt new file mode 100644 index 000000000..64ece30d8 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequest.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationRequest( + val jsonrpc: String, + val method: String, + val params: DeeplWebTranslationRequestParams, + val id: Int +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParams.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParams.kt new file mode 100644 index 000000000..382ad3e35 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParams.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationRequestParams( + val texts: List, + val splitting: String, + val lang: DeeplWebTranslationRequestParamsLang, + val commonJobParams: Map, + val timestamp: Long +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLang.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLang.kt new file mode 100644 index 000000000..0236867d4 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLang.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationRequestParamsLang( + @SerialName("target_lang") val targetLang: String, + @SerialName("source_lang_user_selected") val sourceLangUserSelected: String, + val preference: DeeplWebTranslationRequestParamsLangPreference +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLangPreference.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLangPreference.kt new file mode 100644 index 000000000..6e6b1cdb5 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsLangPreference.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationRequestParamsLangPreference( + val weight: Map +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsText.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsText.kt new file mode 100644 index 000000000..11a72f924 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationRequestParamsText.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationRequestParamsText( + val text: String +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResponse.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResponse.kt new file mode 100644 index 000000000..f655a05e8 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResponse.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationResponse( + val result: DeeplWebTranslationResult = DeeplWebTranslationResult() +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResult.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResult.kt new file mode 100644 index 000000000..5e9e45d65 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationResult.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationResult( + val texts: List = listOf(), + val lang: String = "" +) diff --git a/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationTranslation.kt b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationTranslation.kt new file mode 100644 index 000000000..4006ac291 --- /dev/null +++ b/app/src/main/java/com/bnyro/translate/api/deepl/obj/DeeplWebTranslationTranslation.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 You Apps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.bnyro.translate.api.deepl.obj + +import kotlinx.serialization.Serializable + +@Serializable +data class DeeplWebTranslationTranslation( + val text: String = "" +)