Batoto: Custom regex for cleaning title & fix duplicate manga (#11164)
* Batoto: config custom regex to be removed from title * fix(BatoTo): add original (uncleaned) title to description * verify regex * add real-time validation for custom regex input & update summary on change * Also clean title while browsing/searching * Batoto: Fix duplicate manga due to name changed Close #11037
This commit is contained in:
parent
6824183cf1
commit
8b0be67685
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Bato.to'
|
||||
extClass = '.BatoToFactory'
|
||||
extVersionCode = 55
|
||||
extVersionCode = 56
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,12 @@ package eu.kanade.tachiyomi.extension.all.batoto
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
@ -87,9 +92,49 @@ open class BatoTo(
|
||||
"You might also want to clear the database in advanced settings."
|
||||
setDefaultValue(false)
|
||||
}
|
||||
val removeCustomPref = EditTextPreference(screen.context).apply {
|
||||
key = "${REMOVE_TITLE_CUSTOM_PREF}_$lang"
|
||||
title = "Custom regex to be removed from title"
|
||||
summary = customRemoveTitle()
|
||||
setDefaultValue("")
|
||||
|
||||
val validate = { str: String ->
|
||||
runCatching { Regex(str) }
|
||||
.map { true to "" }
|
||||
.getOrElse { false to it.message }
|
||||
}
|
||||
|
||||
setOnBindEditTextListener { editText ->
|
||||
editText.addTextChangedListener(
|
||||
object : TextWatcher {
|
||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
editable ?: return
|
||||
val text = editable.toString()
|
||||
val valid = validate(text)
|
||||
editText.error = if (!valid.first) valid.second else null
|
||||
editText.rootView.findViewById<Button>(android.R.id.button1)?.isEnabled = editText.error == null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val (isValid, message) = validate(newValue as String)
|
||||
if (isValid) {
|
||||
summary = newValue
|
||||
} else {
|
||||
Toast.makeText(screen.context, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
isValid
|
||||
}
|
||||
}
|
||||
screen.addPreference(mirrorPref)
|
||||
screen.addPreference(altChapterListPref)
|
||||
screen.addPreference(removeOfficialPref)
|
||||
screen.addPreference(removeCustomPref)
|
||||
}
|
||||
|
||||
private fun getMirrorPref(): String {
|
||||
@ -120,12 +165,14 @@ open class BatoTo(
|
||||
private fun isRemoveTitleVersion(): Boolean {
|
||||
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
|
||||
}
|
||||
private fun customRemoveTitle(): String =
|
||||
preferences.getString("${REMOVE_TITLE_CUSTOM_PREF}_$lang", "")!!
|
||||
|
||||
private fun SharedPreferences.migrateMirrorPref() {
|
||||
val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!!
|
||||
|
||||
if (selectedMirror in DEPRECATED_MIRRORS) {
|
||||
edit().putString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE).commit()
|
||||
edit().putString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE).apply()
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +196,9 @@ open class BatoTo(
|
||||
val manga = SManga.create()
|
||||
val item = element.select("a.item-cover")
|
||||
val imgurl = item.select("img").attr("abs:src")
|
||||
manga.setUrlWithoutDomain(item.attr("href"))
|
||||
manga.setUrlWithoutDomain(stripSeriesUrl(item.attr("href")))
|
||||
manga.title = element.select("a.item-title").text().removeEntities()
|
||||
.cleanTitleIfNeeded()
|
||||
manga.thumbnail_url = imgurl
|
||||
return manga
|
||||
}
|
||||
@ -275,9 +323,10 @@ open class BatoTo(
|
||||
val infoElement = document.select("div#mainer div.container-fluid")
|
||||
val manga = SManga.create()
|
||||
manga.title = infoElement.select("h3").text().removeEntities()
|
||||
.cleanTitleIfNeeded()
|
||||
manga.thumbnail_url = document.select("div.attr-cover img")
|
||||
.attr("abs:src")
|
||||
manga.setUrlWithoutDomain(infoElement.select("h3 a").attr("abs:href"))
|
||||
manga.setUrlWithoutDomain(stripSeriesUrl(infoElement.select("h3 a").attr("abs:href")))
|
||||
return MangasPage(listOf(manga), false)
|
||||
}
|
||||
|
||||
@ -308,16 +357,18 @@ open class BatoTo(
|
||||
|
||||
private fun searchUtilsFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
manga.setUrlWithoutDomain(element.select("td a").attr("href"))
|
||||
manga.setUrlWithoutDomain(stripSeriesUrl(element.select("td a").attr("href")))
|
||||
manga.title = element.select("td a").text()
|
||||
.cleanTitleIfNeeded()
|
||||
manga.thumbnail_url = element.select("img").attr("abs:src")
|
||||
return manga
|
||||
}
|
||||
|
||||
private fun searchHistoryFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
manga.setUrlWithoutDomain(element.select(".position-relative a").attr("href"))
|
||||
manga.setUrlWithoutDomain(stripSeriesUrl(element.select(".position-relative a").attr("href")))
|
||||
manga.title = element.select(".position-relative a").text()
|
||||
.cleanTitleIfNeeded()
|
||||
manga.thumbnail_url = element.select("img").attr("abs:src")
|
||||
return manga
|
||||
}
|
||||
@ -346,8 +397,6 @@ open class BatoTo(
|
||||
}
|
||||
return super.mangaDetailsRequest(manga)
|
||||
}
|
||||
private var titleRegex: Regex =
|
||||
Regex("\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|\\/Official|\\/ Official", RegexOption.IGNORE_CASE)
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val infoElement = document.selectFirst("div#mainer div.container-fluid")!!
|
||||
@ -368,19 +417,19 @@ open class BatoTo(
|
||||
append(it.text().split('/').joinToString("\n") { "• ${it.trim()}" })
|
||||
}
|
||||
}.trim()
|
||||
|
||||
val cleanedTitle = if (isRemoveTitleVersion()) {
|
||||
originalTitle.replace(titleRegex, "").trim()
|
||||
} else {
|
||||
originalTitle
|
||||
}
|
||||
val cleanedTitle = originalTitle.cleanTitleIfNeeded()
|
||||
|
||||
manga.title = cleanedTitle
|
||||
manga.author = infoElement.select("div.attr-item:contains(author) span").text()
|
||||
manga.artist = infoElement.select("div.attr-item:contains(artist) span").text()
|
||||
manga.status = parseStatus(workStatus, uploadStatus)
|
||||
manga.genre = infoElement.select(".attr-item b:contains(genres) + span ").joinToString { it.text() }
|
||||
manga.description = description
|
||||
manga.description = if (originalTitle.trim() != cleanedTitle) {
|
||||
listOf(originalTitle, description)
|
||||
.joinToString("\n\n")
|
||||
} else {
|
||||
description
|
||||
}
|
||||
manga.thumbnail_url = document.select("div.attr-cover img").attr("abs:src")
|
||||
return manga
|
||||
}
|
||||
@ -424,9 +473,9 @@ open class BatoTo(
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
return if (getAltChapterListPref()) {
|
||||
val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim()
|
||||
|
||||
val id = seriesIdRegex.find(manga.url)
|
||||
?.groups?.get(1)?.value?.trim()
|
||||
return if (getAltChapterListPref() && !id.isNullOrBlank()) {
|
||||
GET("$baseUrl/rss/series/$id.xml", headers)
|
||||
} else if (manga.url.startsWith("http")) {
|
||||
// Check if trying to use a deprecated mirror, force current mirror
|
||||
@ -571,6 +620,19 @@ open class BatoTo(
|
||||
|
||||
private fun String.removeEntities(): String = Parser.unescapeEntities(this, true)
|
||||
|
||||
private fun String.cleanTitleIfNeeded(): String {
|
||||
var tempTitle = this
|
||||
customRemoveTitle().takeIf { it.isNotEmpty() }?.let { customRegex ->
|
||||
runCatching {
|
||||
tempTitle = tempTitle.replace(Regex(customRegex), "")
|
||||
}
|
||||
}
|
||||
if (isRemoveTitleVersion()) {
|
||||
tempTitle = tempTitle.replace(titleRegex, "")
|
||||
}
|
||||
return tempTitle.trim()
|
||||
}
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
LetterFilter(getLetterFilter(), 0),
|
||||
Filter.Separator(),
|
||||
@ -1026,10 +1088,18 @@ open class BatoTo(
|
||||
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
||||
).filterNot { it.value == siteLang }
|
||||
|
||||
private fun stripSeriesUrl(url: String): String {
|
||||
val matchResult = seriesUrlRegex.find(url)
|
||||
return matchResult?.groups?.get(1)?.value ?: url
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val seriesUrlRegex = Regex("""(.*/series/\d+)/.*""")
|
||||
private val seriesIdRegex = Regex("""series/(\d+)""")
|
||||
private const val MIRROR_PREF_KEY = "MIRROR"
|
||||
private const val MIRROR_PREF_TITLE = "Mirror"
|
||||
private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"
|
||||
private const val REMOVE_TITLE_CUSTOM_PREF = "REMOVE_TITLE_CUSTOM"
|
||||
private val MIRROR_PREF_ENTRIES = arrayOf(
|
||||
"Auto",
|
||||
// https://batotomirrors.pages.dev/
|
||||
@ -1104,5 +1174,8 @@ open class BatoTo(
|
||||
private const val ALT_CHAPTER_LIST_PREF_TITLE = "Alternative Chapter List"
|
||||
private const val ALT_CHAPTER_LIST_PREF_SUMMARY = "If checked, uses an alternate chapter list"
|
||||
private const val ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE = false
|
||||
|
||||
private val titleRegex: Regex =
|
||||
Regex("\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|\uD81A\uDD0D.+?\uD81A\uDD0D|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|/Official|/ Official", RegexOption.IGNORE_CASE)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user