Validate UUID preferences and fix images loading on MangaDex (#13597)
* Validate UUIDs on preferences. * Make the MD@H report call asynchronous.
This commit is contained in:
parent
8fc2741090
commit
c5bd48adc5
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'MangaDex'
|
extName = 'MangaDex'
|
||||||
pkgNameSuffix = 'all.mangadex'
|
pkgNameSuffix = 'all.mangadex'
|
||||||
extClass = '.MangaDexFactory'
|
extClass = '.MangaDexFactory'
|
||||||
extVersionCode = 167
|
extVersionCode = 168
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,10 @@ import java.util.TimeZone
|
||||||
|
|
||||||
object MDConstants {
|
object MDConstants {
|
||||||
|
|
||||||
val uuidRegex =
|
const val UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
|
||||||
Regex("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")
|
|
||||||
|
val uuidRegex = UUID_REGEX.toRegex()
|
||||||
|
val onlyUuidRegex = "^$UUID_REGEX$".toRegex()
|
||||||
|
|
||||||
const val mangaLimit = 20
|
const val mangaLimit = 20
|
||||||
const val latestChapterLimit = 100
|
const val latestChapterLimit = 100
|
||||||
|
@ -103,6 +105,11 @@ object MDConstants {
|
||||||
return "${blockedUploaderPref}_$dexLang"
|
return "${blockedUploaderPref}_$dexLang"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val hasSanitizedUuidsPref = "hasSanitizedUuids"
|
||||||
|
fun getHasSanitizedUuidsPrefKey(dexLang: String): String {
|
||||||
|
return "${hasSanitizedUuidsPref}_$dexLang"
|
||||||
|
}
|
||||||
|
|
||||||
const val tagGroupContent = "content"
|
const val tagGroupContent = "content"
|
||||||
const val tagGroupFormat = "format"
|
const val tagGroupFormat = "format"
|
||||||
const val tagGroupGenre = "genre"
|
const val tagGroupGenre = "genre"
|
||||||
|
|
|
@ -64,6 +64,10 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
init {
|
||||||
|
preferences.sanitizeExistingUuidPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
// POPULAR Manga Section
|
// POPULAR Manga Section
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
@ -745,15 +749,11 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
title = helper.intl.blockGroupByUuid
|
title = helper.intl.blockGroupByUuid
|
||||||
summary = helper.intl.blockGroupByUuidSummary
|
summary = helper.intl.blockGroupByUuidSummary
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnBindEditTextListener(helper::setupEditTextUuidValidator)
|
||||||
val groupsBlocked = newValue.toString()
|
|
||||||
.split(",")
|
|
||||||
.map { it.trim() }
|
|
||||||
.filter { helper.containsUuid(it) }
|
|
||||||
.joinToString(", ")
|
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString(MDConstants.getBlockedGroupsPrefKey(dexLang), groupsBlocked)
|
.putString(MDConstants.getBlockedGroupsPrefKey(dexLang), newValue.toString())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -763,15 +763,11 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
title = helper.intl.blockUploaderByUuid
|
title = helper.intl.blockUploaderByUuid
|
||||||
summary = helper.intl.blockUploaderByUuidSummary
|
summary = helper.intl.blockUploaderByUuidSummary
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnBindEditTextListener(helper::setupEditTextUuidValidator)
|
||||||
val uploaderBlocked = newValue.toString()
|
|
||||||
.split(",")
|
|
||||||
.map { it.trim() }
|
|
||||||
.filter { helper.containsUuid(it) }
|
|
||||||
.joinToString(", ")
|
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), uploaderBlocked)
|
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), newValue.toString())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -795,4 +791,28 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
private inline fun <reified T> Response.parseAs(): T = use {
|
private inline fun <reified T> Response.parseAs(): T = use {
|
||||||
helper.json.decodeFromString(body?.string().orEmpty())
|
helper.json.decodeFromString(body?.string().orEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SharedPreferences.sanitizeExistingUuidPrefs() {
|
||||||
|
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
||||||
|
.split(",")
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(helper::isUuid)
|
||||||
|
.joinToString(", ")
|
||||||
|
|
||||||
|
val blockedUploaders = getString(MDConstants.getBlockedUploaderPrefKey(dexLang), "")!!
|
||||||
|
.split(",")
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(helper::isUuid)
|
||||||
|
.joinToString(", ")
|
||||||
|
|
||||||
|
edit()
|
||||||
|
.putString(MDConstants.getBlockedGroupsPrefKey(dexLang), blockedGroups)
|
||||||
|
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), blockedUploaders)
|
||||||
|
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.mangadex
|
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
|
import eu.kanade.tachiyomi.extension.all.mangadex.dto.AtHomeDto
|
||||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDataDto
|
import eu.kanade.tachiyomi.extension.all.mangadex.dto.ChapterDataDto
|
||||||
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaAttributesDto
|
import eu.kanade.tachiyomi.extension.all.mangadex.dto.MangaAttributesDto
|
||||||
|
@ -19,7 +23,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.parser.Parser
|
import org.jsoup.parser.Parser
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -62,6 +65,11 @@ class MangaDexHelper(private val lang: String) {
|
||||||
*/
|
*/
|
||||||
fun containsUuid(url: String) = url.contains(MDConstants.uuidRegex)
|
fun containsUuid(url: String) = url.contains(MDConstants.uuidRegex)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the string is a valid uuid
|
||||||
|
*/
|
||||||
|
fun isUuid(text: String) = text.matches(MDConstants.onlyUuidRegex)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the manga offset pages are 1 based, so subtract 1
|
* Get the manga offset pages are 1 based, so subtract 1
|
||||||
*/
|
*/
|
||||||
|
@ -394,4 +402,33 @@ class MangaDexHelper(private val lang: String) {
|
||||||
currentSlug
|
currentSlug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom [TextWatcher] to the preference's [EditText] that show an
|
||||||
|
* error if the input value contains invalid UUIDs. If the validation fails,
|
||||||
|
* the Ok button is disabled to prevent the user from saving the value.
|
||||||
|
*/
|
||||||
|
fun setupEditTextUuidValidator(editText: EditText) {
|
||||||
|
editText.addTextChangedListener(object : TextWatcher {
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(editable: Editable?) {
|
||||||
|
requireNotNull(editable)
|
||||||
|
|
||||||
|
val text = editable.toString()
|
||||||
|
|
||||||
|
val isValid = text.isBlank() || text
|
||||||
|
.split(",")
|
||||||
|
.map(String::trim)
|
||||||
|
.all(::isUuid)
|
||||||
|
|
||||||
|
editText.error = if (!isValid) intl.invalidUuids else null
|
||||||
|
editText.rootView.findViewById<Button>(android.R.id.button1)
|
||||||
|
?.isEnabled = editText.error == null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,19 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class MangaDexIntl(val lang: String) {
|
class MangaDexIntl(lang: String) {
|
||||||
|
|
||||||
val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH
|
val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH
|
||||||
|
|
||||||
val locale: Locale = Locale.forLanguageTag(availableLang)
|
private val locale: Locale = Locale.forLanguageTag(availableLang)
|
||||||
|
|
||||||
val collator: Collator = Collator.getInstance(locale)
|
val collator: Collator = Collator.getInstance(locale)
|
||||||
|
|
||||||
|
val invalidUuids: String = when (availableLang) {
|
||||||
|
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "O texto contém UUIDs inválidos"
|
||||||
|
else -> "The text contains invalid UUIDs"
|
||||||
|
}
|
||||||
|
|
||||||
val invalidGroupId: String = when (availableLang) {
|
val invalidGroupId: String = when (availableLang) {
|
||||||
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do grupo inválido"
|
BRAZILIAN_PORTUGUESE, PORTUGUESE -> "ID do grupo inválido"
|
||||||
else -> "Not a valid group ID"
|
else -> "Not a valid group ID"
|
||||||
|
|
|
@ -5,6 +5,8 @@ import eu.kanade.tachiyomi.extension.all.mangadex.dto.ImageReportDto
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
@ -12,6 +14,7 @@ import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interceptor to post to md@home for MangaDex Stats
|
* Interceptor to post to md@home for MangaDex Stats
|
||||||
|
@ -28,37 +31,46 @@ class MdAtHomeReportInterceptor(
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
|
val response = chain.proceed(chain.request())
|
||||||
return chain.proceed(chain.request()).let { response ->
|
|
||||||
val url = originalRequest.url.toString()
|
val url = originalRequest.url.toString()
|
||||||
if (url.contains(mdAtHomeUrlRegex)) {
|
|
||||||
val byteSize = response.peekBody(Long.MAX_VALUE).bytes().size
|
if (!url.contains(mdAtHomeUrlRegex)) {
|
||||||
val duration = response.receivedResponseAtMillis - response.sentRequestAtMillis
|
return response
|
||||||
val cache = response.header("X-Cache", "") == "HIT"
|
}
|
||||||
|
|
||||||
val result = ImageReportDto(
|
val result = ImageReportDto(
|
||||||
url,
|
url,
|
||||||
response.isSuccessful,
|
success = response.isSuccessful,
|
||||||
byteSize,
|
bytes = response.peekBody(Long.MAX_VALUE).bytes().size,
|
||||||
cache,
|
cached = response.header("X-Cache", "") == "HIT",
|
||||||
duration
|
duration = response.receivedResponseAtMillis - response.sentRequestAtMillis
|
||||||
)
|
)
|
||||||
|
|
||||||
val jsonString = json.encodeToString(result)
|
val payload = json.encodeToString(result)
|
||||||
|
|
||||||
try {
|
val reportRequest = POST(
|
||||||
client.newCall(
|
url = MDConstants.atHomePostUrl,
|
||||||
POST(
|
headers = headers,
|
||||||
MDConstants.atHomePostUrl,
|
body = payload.toRequestBody(JSON_MEDIA_TYPE)
|
||||||
headers,
|
|
||||||
jsonString.toRequestBody("application/json".toMediaType())
|
|
||||||
)
|
)
|
||||||
).execute().close()
|
|
||||||
} catch (e: Exception) {
|
// Execute the report endpoint network call asynchronously to avoid blocking
|
||||||
|
// the reader from showing the image once it's fully loaded if the report call
|
||||||
|
// gets stuck, as it tend to happens sometimes.
|
||||||
|
client.newCall(reportRequest).enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}")
|
Log.e("MangaDex", "Error trying to POST report to MD@Home: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
response.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
companion object {
|
||||||
}
|
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue