Skip to content

Commit

Permalink
Merge pull request #428 from papjul/deepl_web
Browse files Browse the repository at this point in the history
[DeepL] Implement web API fallback
  • Loading branch information
Bnyro authored May 23, 2024
2 parents 53cd1d1 + f50f6f6 commit fe5f7c7
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 28 deletions.
21 changes: 21 additions & 0 deletions app/src/main/java/com/bnyro/translate/api/deepl/DeepL.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
151 changes: 124 additions & 27 deletions app/src/main/java/com/bnyro/translate/api/deepl/DeeplEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,156 @@

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)
}

private val apiKeyString = "DeepL-Auth-Key " + getApiKey()

override suspend fun getLanguages(): List<Language> = api.getLanguages(
apiKeyString
).map {
Language(
code = it.language.lowercase(),
name = it.name
)
}.toMutableList().apply {
add(
0,
Language(
"",
"Auto"
override suspend fun getLanguages(): List<Language> = 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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

package com.bnyro.translate.api.deepl.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeeplWebTranslationRequestParams(
val texts: List<DeeplWebTranslationRequestParamsText>,
val splitting: String,
val lang: DeeplWebTranslationRequestParamsLang,
val commonJobParams: Map<String, String>,
val timestamp: Long
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

package com.bnyro.translate.api.deepl.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeeplWebTranslationRequestParamsLangPreference(
val weight: Map<String, String>
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

package com.bnyro.translate.api.deepl.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeeplWebTranslationRequestParamsText(
val text: String
)
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

package com.bnyro.translate.api.deepl.obj

import kotlinx.serialization.Serializable

@Serializable
data class DeeplWebTranslationResponse(
val result: DeeplWebTranslationResult = DeeplWebTranslationResult()
)
Loading

0 comments on commit fe5f7c7

Please sign in to comment.