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 {
|
ext {
|
||||||
extName = 'Bato.to'
|
extName = 'Bato.to'
|
||||||
extClass = '.BatoToFactory'
|
extClass = '.BatoToFactory'
|
||||||
extVersionCode = 55
|
extVersionCode = 56
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,12 @@ package eu.kanade.tachiyomi.extension.all.batoto
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
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.CheckBoxPreference
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||||
@ -87,9 +92,49 @@ open class BatoTo(
|
|||||||
"You might also want to clear the database in advanced settings."
|
"You might also want to clear the database in advanced settings."
|
||||||
setDefaultValue(false)
|
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(mirrorPref)
|
||||||
screen.addPreference(altChapterListPref)
|
screen.addPreference(altChapterListPref)
|
||||||
screen.addPreference(removeOfficialPref)
|
screen.addPreference(removeOfficialPref)
|
||||||
|
screen.addPreference(removeCustomPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMirrorPref(): String {
|
private fun getMirrorPref(): String {
|
||||||
@ -120,12 +165,14 @@ open class BatoTo(
|
|||||||
private fun isRemoveTitleVersion(): Boolean {
|
private fun isRemoveTitleVersion(): Boolean {
|
||||||
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
|
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
|
||||||
}
|
}
|
||||||
|
private fun customRemoveTitle(): String =
|
||||||
|
preferences.getString("${REMOVE_TITLE_CUSTOM_PREF}_$lang", "")!!
|
||||||
|
|
||||||
private fun SharedPreferences.migrateMirrorPref() {
|
private fun SharedPreferences.migrateMirrorPref() {
|
||||||
val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!!
|
val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!!
|
||||||
|
|
||||||
if (selectedMirror in DEPRECATED_MIRRORS) {
|
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 manga = SManga.create()
|
||||||
val item = element.select("a.item-cover")
|
val item = element.select("a.item-cover")
|
||||||
val imgurl = item.select("img").attr("abs:src")
|
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()
|
manga.title = element.select("a.item-title").text().removeEntities()
|
||||||
|
.cleanTitleIfNeeded()
|
||||||
manga.thumbnail_url = imgurl
|
manga.thumbnail_url = imgurl
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
@ -275,9 +323,10 @@ open class BatoTo(
|
|||||||
val infoElement = document.select("div#mainer div.container-fluid")
|
val infoElement = document.select("div#mainer div.container-fluid")
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
manga.title = infoElement.select("h3").text().removeEntities()
|
manga.title = infoElement.select("h3").text().removeEntities()
|
||||||
|
.cleanTitleIfNeeded()
|
||||||
manga.thumbnail_url = document.select("div.attr-cover img")
|
manga.thumbnail_url = document.select("div.attr-cover img")
|
||||||
.attr("abs:src")
|
.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)
|
return MangasPage(listOf(manga), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,16 +357,18 @@ open class BatoTo(
|
|||||||
|
|
||||||
private fun searchUtilsFromElement(element: Element): SManga {
|
private fun searchUtilsFromElement(element: Element): SManga {
|
||||||
val manga = SManga.create()
|
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()
|
manga.title = element.select("td a").text()
|
||||||
|
.cleanTitleIfNeeded()
|
||||||
manga.thumbnail_url = element.select("img").attr("abs:src")
|
manga.thumbnail_url = element.select("img").attr("abs:src")
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchHistoryFromElement(element: Element): SManga {
|
private fun searchHistoryFromElement(element: Element): SManga {
|
||||||
val manga = SManga.create()
|
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()
|
manga.title = element.select(".position-relative a").text()
|
||||||
|
.cleanTitleIfNeeded()
|
||||||
manga.thumbnail_url = element.select("img").attr("abs:src")
|
manga.thumbnail_url = element.select("img").attr("abs:src")
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
@ -346,8 +397,6 @@ open class BatoTo(
|
|||||||
}
|
}
|
||||||
return super.mangaDetailsRequest(manga)
|
return super.mangaDetailsRequest(manga)
|
||||||
}
|
}
|
||||||
private var titleRegex: Regex =
|
|
||||||
Regex("\\([^()]*\\)|\\{[^{}]*\\}|\\[(?:(?!]).)*]|«[^»]*»|〘[^〙]*〙|「[^」]*」|『[^』]*』|≪[^≫]*≫|﹛[^﹜]*﹜|〖[^〖〗]*〗|𖤍.+?𖤍|《[^》]*》|⌜.+?⌝|⟨[^⟩]*⟩|\\/Official|\\/ Official", RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val infoElement = document.selectFirst("div#mainer div.container-fluid")!!
|
val infoElement = document.selectFirst("div#mainer div.container-fluid")!!
|
||||||
@ -368,19 +417,19 @@ open class BatoTo(
|
|||||||
append(it.text().split('/').joinToString("\n") { "• ${it.trim()}" })
|
append(it.text().split('/').joinToString("\n") { "• ${it.trim()}" })
|
||||||
}
|
}
|
||||||
}.trim()
|
}.trim()
|
||||||
|
val cleanedTitle = originalTitle.cleanTitleIfNeeded()
|
||||||
val cleanedTitle = if (isRemoveTitleVersion()) {
|
|
||||||
originalTitle.replace(titleRegex, "").trim()
|
|
||||||
} else {
|
|
||||||
originalTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
manga.title = cleanedTitle
|
manga.title = cleanedTitle
|
||||||
manga.author = infoElement.select("div.attr-item:contains(author) span").text()
|
manga.author = infoElement.select("div.attr-item:contains(author) span").text()
|
||||||
manga.artist = infoElement.select("div.attr-item:contains(artist) span").text()
|
manga.artist = infoElement.select("div.attr-item:contains(artist) span").text()
|
||||||
manga.status = parseStatus(workStatus, uploadStatus)
|
manga.status = parseStatus(workStatus, uploadStatus)
|
||||||
manga.genre = infoElement.select(".attr-item b:contains(genres) + span ").joinToString { it.text() }
|
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")
|
manga.thumbnail_url = document.select("div.attr-cover img").attr("abs:src")
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
@ -424,9 +473,9 @@ open class BatoTo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
return if (getAltChapterListPref()) {
|
val id = seriesIdRegex.find(manga.url)
|
||||||
val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim()
|
?.groups?.get(1)?.value?.trim()
|
||||||
|
return if (getAltChapterListPref() && !id.isNullOrBlank()) {
|
||||||
GET("$baseUrl/rss/series/$id.xml", headers)
|
GET("$baseUrl/rss/series/$id.xml", headers)
|
||||||
} else if (manga.url.startsWith("http")) {
|
} else if (manga.url.startsWith("http")) {
|
||||||
// Check if trying to use a deprecated mirror, force current mirror
|
// 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.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(
|
override fun getFilterList() = FilterList(
|
||||||
LetterFilter(getLetterFilter(), 0),
|
LetterFilter(getLetterFilter(), 0),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
@ -1026,10 +1088,18 @@ open class BatoTo(
|
|||||||
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
|
||||||
).filterNot { it.value == siteLang }
|
).filterNot { it.value == siteLang }
|
||||||
|
|
||||||
|
private fun stripSeriesUrl(url: String): String {
|
||||||
|
val matchResult = seriesUrlRegex.find(url)
|
||||||
|
return matchResult?.groups?.get(1)?.value ?: url
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
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_KEY = "MIRROR"
|
||||||
private const val MIRROR_PREF_TITLE = "Mirror"
|
private const val MIRROR_PREF_TITLE = "Mirror"
|
||||||
private const val REMOVE_TITLE_VERSION_PREF = "REMOVE_TITLE_VERSION"
|
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(
|
private val MIRROR_PREF_ENTRIES = arrayOf(
|
||||||
"Auto",
|
"Auto",
|
||||||
// https://batotomirrors.pages.dev/
|
// 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_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_SUMMARY = "If checked, uses an alternate chapter list"
|
||||||
private const val ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE = false
|
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