MachineTranslations & Snowmtl: Fix the regex in the translator engine and add font size settings (#7465)
* Fix the regex in the translator engine and add font size settings * Remove extra lines * Remove rateLimit * Remove init pref * Use lazy statement in the snowmtl client * Relax the exception and show pages without dialog * Fix the translator's bad formatted response for some cases * Change listener return to false Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
e080f6fd1b
commit
0505a26934
|
@ -0,0 +1,4 @@
|
||||||
|
font_size_title=Font size
|
||||||
|
font_size_summary=Font changes will not be applied to downloaded or cached chapters. The font size will be adjusted according to the size of the dialog box.
|
||||||
|
font_size_message=Font size changed to %s
|
||||||
|
default_font_size=Default
|
|
@ -0,0 +1 @@
|
||||||
|
font_size_title=Tamaño de letra
|
|
@ -0,0 +1 @@
|
||||||
|
font_size_title=Taille de la police
|
|
@ -0,0 +1 @@
|
||||||
|
font_size_title=Ukuran font
|
|
@ -0,0 +1 @@
|
||||||
|
font_size_title=Dimensione del carattere
|
|
@ -0,0 +1,4 @@
|
||||||
|
font_size_title=Tamanho da fonte
|
||||||
|
font_size_summary=As alterações de fonte não serão aplicadas aos capítulos baixados ou armazenados em cache. O tamanho da fonte será ajustado de acordo com o tamanho da caixa de diálogo.
|
||||||
|
font_size_message=Tamanho da fonte foi alterada para %s
|
||||||
|
default_font_size=Padrão
|
|
@ -2,4 +2,8 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 2
|
baseVersionCode = 3
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":lib:i18n"))
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.machinetranslations
|
package eu.kanade.tachiyomi.multisrc.machinetranslations
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.lib.i18n.Intl
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
@ -15,10 +22,13 @@ import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
@ -29,7 +39,7 @@ abstract class MachineTranslations(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
val language: Language,
|
val language: Language,
|
||||||
) : ParsedHttpSource() {
|
) : ParsedHttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
@ -37,9 +47,26 @@ abstract class MachineTranslations(
|
||||||
|
|
||||||
override val lang = language.lang
|
override val lang = language.lang
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
private val preferences: SharedPreferences by lazy {
|
||||||
.addInterceptor(ComposedImageInterceptor(baseUrl, language))
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var fontSize: Int
|
||||||
|
get() = preferences.getString(FONT_SIZE_PREF, DEFAULT_FONT_SIZE)!!.toInt()
|
||||||
|
set(value) = preferences.edit().putString(FONT_SIZE_PREF, value.toString()).apply()
|
||||||
|
|
||||||
|
private val intl = Intl(
|
||||||
|
language = language.lang,
|
||||||
|
baseLanguage = "en",
|
||||||
|
availableLanguages = setOf("en", "es", "fr", "id", "it", "pt-BR"),
|
||||||
|
classLoader = this::class.java.classLoader!!,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val client: OkHttpClient by lazy {
|
||||||
|
network.cloudflareClient.newBuilder()
|
||||||
|
.addInterceptor(ComposedImageInterceptor(baseUrl, language, fontSize))
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
|
|
||||||
|
@ -203,9 +230,49 @@ abstract class MachineTranslations(
|
||||||
return FilterList(filters)
|
return FilterList(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
// Some libreoffice font sizes
|
||||||
|
val sizes = arrayOf(
|
||||||
|
"24", "26", "28",
|
||||||
|
"32", "36", "40",
|
||||||
|
"42", "44", "48",
|
||||||
|
"54", "60", "72",
|
||||||
|
"80", "88", "96",
|
||||||
|
)
|
||||||
|
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
key = FONT_SIZE_PREF
|
||||||
|
title = intl["font_size_title"]
|
||||||
|
entries = sizes.map {
|
||||||
|
"${it}pt" + if (it == DEFAULT_FONT_SIZE) " - ${intl["default_font_size"]}" else ""
|
||||||
|
}.toTypedArray()
|
||||||
|
entryValues = sizes
|
||||||
|
summary = intl["font_size_summary"]
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val selected = newValue as String
|
||||||
|
val index = this.findIndexOfValue(selected)
|
||||||
|
val entry = entries[index] as String
|
||||||
|
|
||||||
|
fontSize = selected.toInt()
|
||||||
|
|
||||||
|
Toast.makeText(
|
||||||
|
screen.context,
|
||||||
|
intl["font_size_message"].format(entry),
|
||||||
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
|
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
|
||||||
const val PREFIX_SEARCH = "id:"
|
const val PREFIX_SEARCH = "id:"
|
||||||
|
const val FONT_SIZE_PREF = "fontSizePref"
|
||||||
|
const val DEFAULT_FONT_SIZE = "24"
|
||||||
|
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US)
|
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
@ -36,6 +35,7 @@ import kotlin.math.sqrt
|
||||||
class ComposedImageInterceptor(
|
class ComposedImageInterceptor(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
val language: Language,
|
val language: Language,
|
||||||
|
val fontSize: Int = 24,
|
||||||
) : Interceptor {
|
) : Interceptor {
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
@ -55,7 +55,7 @@ class ComposedImageInterceptor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialogues = request.url.fragment?.parseAs<List<Dialog>>()
|
val dialogues = request.url.fragment?.parseAs<List<Dialog>>()
|
||||||
?: throw IOException("Dialogues not found")
|
?: emptyList()
|
||||||
|
|
||||||
val imageRequest = request.newBuilder()
|
val imageRequest = request.newBuilder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -104,7 +104,7 @@ class ComposedImageInterceptor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTextPaint(font: Typeface?): TextPaint {
|
private fun createTextPaint(font: Typeface?): TextPaint {
|
||||||
val defaultTextSize = 24.pt // arbitrary
|
val defaultTextSize = fontSize.pt
|
||||||
return TextPaint().apply {
|
return TextPaint().apply {
|
||||||
color = Color.BLACK
|
color = Color.BLACK
|
||||||
textSize = defaultTextSize
|
textSize = defaultTextSize
|
||||||
|
|
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.interceptors.ComposedImageInterceptor
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
@ -27,10 +28,12 @@ class Snowmtl(
|
||||||
|
|
||||||
private val translator: TranslatorEngine = BingTranslator(clientUtils, headers)
|
private val translator: TranslatorEngine = BingTranslator(clientUtils, headers)
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient by lazy {
|
||||||
.rateLimit(2)
|
network.cloudflareClient.newBuilder()
|
||||||
|
.connectTimeout(1, TimeUnit.MINUTES)
|
||||||
.readTimeout(2, TimeUnit.MINUTES)
|
.readTimeout(2, TimeUnit.MINUTES)
|
||||||
.addInterceptor(TranslationInterceptor(language, translator))
|
.addInterceptor(TranslationInterceptor(language, translator))
|
||||||
.addInterceptor(ComposedImageInterceptor(baseUrl, language))
|
.addInterceptor(ComposedImageInterceptor(baseUrl, language, fontSize))
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,20 @@ class TranslationInterceptor(
|
||||||
tokens: List<String>,
|
tokens: List<String>,
|
||||||
mapping: Map<String, Pair<String, AssociatedDialog>>,
|
mapping: Map<String, Pair<String, AssociatedDialog>>,
|
||||||
) = tokens.mapNotNull { token ->
|
) = tokens.mapNotNull { token ->
|
||||||
val list = token.decode().parseAs<List<String>>()
|
val list = try {
|
||||||
|
token.decode().parseAs<List<String>>()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// The translator may return an invalid JSON, but it keeps the pattern sent.
|
||||||
|
TRANSLATOR_EXTRACT_REGEX.findAll(token).map {
|
||||||
|
listOf(
|
||||||
|
it.groups[1]?.value!!.encode(),
|
||||||
|
it.groups[3]?.value!!.let { dialog ->
|
||||||
|
dialog.takeIf { it.startsWith("\"") } ?: dialog.encode()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}.toList().flatten()
|
||||||
|
}
|
||||||
|
|
||||||
val key = list.first()
|
val key = list.first()
|
||||||
val text = list.last()
|
val text = list.last()
|
||||||
|
|
||||||
|
@ -102,7 +115,7 @@ class TranslationInterceptor(
|
||||||
private fun String.encode() = "\"${this}\""
|
private fun String.encode() = "\"${this}\""
|
||||||
private fun String.decode() = this.substringAfter("\"").substringBeforeLast("\"")
|
private fun String.decode() = this.substringAfter("\"").substringBeforeLast("\"")
|
||||||
|
|
||||||
private val delimiter: String = "|"
|
private val delimiter: String = "¦"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tokenizes a list of texts based on the translator's character capacity per request
|
* Tokenizes a list of texts based on the translator's character capacity per request
|
||||||
|
@ -137,6 +150,10 @@ class TranslationInterceptor(
|
||||||
private inline fun <reified T> String.parseAs(): T {
|
private inline fun <reified T> String.parseAs(): T {
|
||||||
return json.decodeFromString(this)
|
return json.decodeFromString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRANSLATOR_EXTRACT_REGEX = """"?(-?\d+)(\\?")?,((\\?")?([^(\])]+))""".toRegex()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AssociatedDialog(
|
private class AssociatedDialog(
|
||||||
|
|
|
@ -97,8 +97,8 @@ class BingTranslator(private val client: OkHttpClient, private val headers: Head
|
||||||
val matchTwo = IG_PARAM_REGEX.find(scriptTwo)?.groups
|
val matchTwo = IG_PARAM_REGEX.find(scriptTwo)?.groups
|
||||||
|
|
||||||
return TokenGroup(
|
return TokenGroup(
|
||||||
token = matchOne?.get(2)?.value ?: "",
|
token = matchOne?.get(4)?.value ?: "",
|
||||||
key = matchOne?.get(1)?.value ?: "",
|
key = matchOne?.get(3)?.value ?: "",
|
||||||
ig = matchTwo?.get(1)?.value ?: "",
|
ig = matchTwo?.get(1)?.value ?: "",
|
||||||
iid = document.selectFirst("div[data-iid]:not([class])")?.attr("data-iid") ?: "",
|
iid = document.selectFirst("div[data-iid]:not([class])")?.attr("data-iid") ?: "",
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.annotation.RequiresApi
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.Language
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.MachineTranslations
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
class Solarmtl(
|
class Solarmtl(
|
||||||
|
@ -15,6 +16,7 @@ class Solarmtl(
|
||||||
language,
|
language,
|
||||||
) {
|
) {
|
||||||
override val client = super.client.newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
|
.connectTimeout(1, TimeUnit.MINUTES)
|
||||||
.rateLimit(2)
|
.rateLimit(2)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue