Bato.to added alternative chapter list and one more filter (#10393)
* Added alternate chapter list and one more filter * Update build.gradle
This commit is contained in:
parent
30cd5d47fb
commit
6146e0fd83
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'Bato.to'
|
extName = 'Bato.to'
|
||||||
pkgNameSuffix = 'all.batoto'
|
pkgNameSuffix = 'all.batoto'
|
||||||
extClass = '.BatoToFactory'
|
extClass = '.BatoToFactory'
|
||||||
extVersionCode = 18
|
extVersionCode = 19
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +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 androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import com.squareup.duktape.Duktape
|
import com.squareup.duktape.Duktape
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
@ -16,13 +18,18 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.parser.Parser
|
import org.jsoup.parser.Parser
|
||||||
|
@ -30,7 +37,9 @@ import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
open class BatoTo(
|
open class BatoTo(
|
||||||
|
@ -43,7 +52,7 @@ open class BatoTo(
|
||||||
}
|
}
|
||||||
|
|
||||||
override val name: String = "Bato.to"
|
override val name: String = "Bato.to"
|
||||||
override val baseUrl: String = getResolutionPref()!!
|
override val baseUrl: String = getMirrorPref()!!
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
val mirrorPref = ListPreference(screen.context).apply {
|
val mirrorPref = ListPreference(screen.context).apply {
|
||||||
|
@ -61,10 +70,23 @@ open class BatoTo(
|
||||||
preferences.edit().putString("${MIRROR_PREF_KEY}_$lang", entry).commit()
|
preferences.edit().putString("${MIRROR_PREF_KEY}_$lang", entry).commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val altChapterListPref = CheckBoxPreference(screen.context).apply {
|
||||||
|
key = "${ALT_CHAPTER_LIST_PREF_KEY}_$lang"
|
||||||
|
title = ALT_CHAPTER_LIST_PREF_TITLE
|
||||||
|
summary = ALT_CHAPTER_LIST_PREF_SUMMARY
|
||||||
|
setDefaultValue(ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE)
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val checkValue = newValue as Boolean
|
||||||
|
preferences.edit().putBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", checkValue).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
screen.addPreference(mirrorPref)
|
screen.addPreference(mirrorPref)
|
||||||
|
screen.addPreference(altChapterListPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getResolutionPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)
|
private fun getMirrorPref(): String? = preferences.getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)
|
||||||
|
private fun getAltChapterListPref(): Boolean = preferences.getBoolean("${ALT_CHAPTER_LIST_PREF_KEY}_$lang", ALT_CHAPTER_LIST_PREF_DEFAULT_VALUE)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MIRROR_PREF_KEY = "MIRROR"
|
private const val MIRROR_PREF_KEY = "MIRROR"
|
||||||
|
@ -72,6 +94,11 @@ open class BatoTo(
|
||||||
private val MIRROR_PREF_ENTRIES = arrayOf("Bato.to", "Batotoo.com", "Comiko.net", "Battwo.com", "Mangatoto.com", "Mycdhands.com")
|
private val MIRROR_PREF_ENTRIES = arrayOf("Bato.to", "Batotoo.com", "Comiko.net", "Battwo.com", "Mangatoto.com", "Mycdhands.com")
|
||||||
private val MIRROR_PREF_ENTRY_VALUES = arrayOf("https://bato.to", "https://batotoo.com", "https://comiko.net", "https://battwo.com", "https://mangatoto.com", "https://mycdhands.com")
|
private val MIRROR_PREF_ENTRY_VALUES = arrayOf("https://bato.to", "https://batotoo.com", "https://comiko.net", "https://battwo.com", "https://mangatoto.com", "https://mycdhands.com")
|
||||||
private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0]
|
private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0]
|
||||||
|
|
||||||
|
private const val ALT_CHAPTER_LIST_PREF_KEY = "ALT_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_DEFAULT_VALUE = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
@ -118,6 +145,7 @@ open class BatoTo(
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
val utilsFilter = filters.findInstance<UtilsFilter>()!!
|
val utilsFilter = filters.findInstance<UtilsFilter>()!!
|
||||||
val letterFilter = filters.findInstance<LetterFilter>()!!
|
val letterFilter = filters.findInstance<LetterFilter>()!!
|
||||||
|
val historyFilter = filters.findInstance<HistoryFilter>()!!
|
||||||
return when {
|
return when {
|
||||||
query.startsWith("ID:") -> {
|
query.startsWith("ID:") -> {
|
||||||
val id = query.substringAfter("ID:")
|
val id = query.substringAfter("ID:")
|
||||||
|
@ -147,6 +175,13 @@ open class BatoTo(
|
||||||
queryUtilsParse(response)
|
queryUtilsParse(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
historyFilter.state != 0 -> {
|
||||||
|
val url = "$baseUrl/ajax.my.${historyFilter.selected}.paging"
|
||||||
|
client.newCall(POST(url, headers, formBuilder().build())).asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
queryHistoryParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val sortFilter = filters.findInstance<SortFilter>()!!
|
val sortFilter = filters.findInstance<SortFilter>()!!
|
||||||
val reverseSortFilter = filters.findInstance<ReverseSortFilter>()!!
|
val reverseSortFilter = filters.findInstance<ReverseSortFilter>()!!
|
||||||
|
@ -228,14 +263,39 @@ open class BatoTo(
|
||||||
return MangasPage(mangas, false)
|
return MangasPage(mangas, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun queryHistoryParse(response: Response): MangasPage {
|
||||||
|
val json = json.decodeFromString<JsonObject>(response.body!!.string())
|
||||||
|
val html = json.jsonObject["html"]!!.jsonPrimitive.content
|
||||||
|
|
||||||
|
val document = Jsoup.parse(html, response.request.url.toString())
|
||||||
|
val mangas = document.select(".my-history-item")
|
||||||
|
.map { element -> searchHistoryFromElement(element) }
|
||||||
|
return MangasPage(mangas, false)
|
||||||
|
}
|
||||||
|
|
||||||
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(element.select("td a").attr("href"))
|
||||||
manga.title = element.select("td a").text().removeEntities()
|
manga.title = element.select("td a").text()
|
||||||
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 {
|
||||||
|
val manga = SManga.create()
|
||||||
|
manga.setUrlWithoutDomain(element.select(".position-relative a").attr("href"))
|
||||||
|
manga.title = element.select(".position-relative a").text()
|
||||||
|
manga.thumbnail_url = element.select("img").attr("abs:src")
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun formBuilder() = FormBody.Builder().apply {
|
||||||
|
add("_where", "browse")
|
||||||
|
add("first", "0")
|
||||||
|
add("limit", "0")
|
||||||
|
add("prevPos", "null")
|
||||||
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used")
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used")
|
||||||
override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
|
override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
|
||||||
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
|
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
|
||||||
|
@ -257,7 +317,7 @@ open class BatoTo(
|
||||||
manga.artist = infoElement.select("div.attr-item:contains(author) a:last-child").text()
|
manga.artist = infoElement.select("div.attr-item:contains(author) a:last-child").text()
|
||||||
manga.status = parseStatus(status)
|
manga.status = parseStatus(status)
|
||||||
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 = infoElement.select("div.limit-html").text() + "\n\n" + infoElement.select(".episode-list > .alert-warning").text()
|
manga.description = infoElement.select("div.limit-html").text() + "\n" + infoElement.select(".episode-list > .alert-warning").text().trim()
|
||||||
manga.thumbnail_url = document.select("div.attr-cover img")
|
manga.thumbnail_url = document.select("div.attr-cover img")
|
||||||
.attr("abs:src")
|
.attr("abs:src")
|
||||||
return manga
|
return manga
|
||||||
|
@ -270,6 +330,39 @@ open class BatoTo(
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
val url = client.newCall(
|
||||||
|
GET(
|
||||||
|
when {
|
||||||
|
manga.url.startsWith("http") -> manga.url
|
||||||
|
else -> "$baseUrl${manga.url}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).execute().asJsoup()
|
||||||
|
if (getAltChapterListPref() || checkChapterLists(url)) {
|
||||||
|
val id = manga.url.substringBeforeLast("/").substringAfterLast("/").trim()
|
||||||
|
return client.newCall(GET("$baseUrl/rss/series/$id.xml"))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { altChapterParse(it, manga.title) }
|
||||||
|
}
|
||||||
|
return super.fetchChapterList(manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun altChapterParse(response: Response, title: String): List<SChapter> {
|
||||||
|
return Jsoup.parse(response.body!!.string(), response.request.url.toString(), Parser.xmlParser())
|
||||||
|
.select("channel > item").map { item ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = item.selectFirst("guid").text()
|
||||||
|
name = item.selectFirst("title").text().substringAfter(title).trim()
|
||||||
|
date_upload = SimpleDateFormat("E, dd MMM yyyy H:m:s Z", Locale.US).parse(item.selectFirst("pubDate").text())?.time ?: 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkChapterLists(document: Document): Boolean {
|
||||||
|
return document.select(".episode-list > .alert-warning").text().contains("This comic has been marked as deleted and the chapter list is not available.")
|
||||||
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
if (manga.url.startsWith("http")) {
|
if (manga.url.startsWith("http")) {
|
||||||
return GET(manga.url, headers)
|
return GET(manga.url, headers)
|
||||||
|
@ -404,6 +497,7 @@ open class BatoTo(
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
LetterFilter(getLetterFilter(), 0),
|
LetterFilter(getLetterFilter(), 0),
|
||||||
|
Filter.Separator(),
|
||||||
Filter.Header("NOTE: Ignored if using text search!"),
|
Filter.Header("NOTE: Ignored if using text search!"),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
SortFilter(getSortFilter(), 5),
|
SortFilter(getSortFilter(), 5),
|
||||||
|
@ -416,7 +510,10 @@ open class BatoTo(
|
||||||
ReverseSortFilter(),
|
ReverseSortFilter(),
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header("NOTE: Filters below are incompatible with any other filters!"),
|
Filter.Header("NOTE: Filters below are incompatible with any other filters!"),
|
||||||
|
Filter.Header("NOTE: Login Required!"),
|
||||||
|
Filter.Separator(),
|
||||||
UtilsFilter(getUtilsFilter(), 0),
|
UtilsFilter(getUtilsFilter(), 0),
|
||||||
|
HistoryFilter(getHistoryFilter(), 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SelectFilterOption(val name: String, val value: String)
|
class SelectFilterOption(val name: String, val value: String)
|
||||||
|
@ -453,11 +550,13 @@ open class BatoTo(
|
||||||
class LangGroupFilter(options: List<CheckboxFilterOption>) : CheckboxGroupFilter("Languages", options)
|
class LangGroupFilter(options: List<CheckboxFilterOption>) : CheckboxGroupFilter("Languages", options)
|
||||||
class LetterFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Letter matching mode (Slow)", options, default)
|
class LetterFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Letter matching mode (Slow)", options, default)
|
||||||
class UtilsFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Utils comic list", options, default)
|
class UtilsFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Utils comic list", options, default)
|
||||||
|
class HistoryFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Personal list", options, default)
|
||||||
|
|
||||||
private fun getLetterFilter() = listOf(
|
private fun getLetterFilter() = listOf(
|
||||||
SelectFilterOption("Disabled", "disabled"),
|
SelectFilterOption("Disabled", "disabled"),
|
||||||
SelectFilterOption("Enabled", "enabled"),
|
SelectFilterOption("Enabled", "enabled"),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getSortFilter() = listOf(
|
private fun getSortFilter() = listOf(
|
||||||
SelectFilterOption("Z-A", "title"),
|
SelectFilterOption("Z-A", "title"),
|
||||||
SelectFilterOption("Last Updated", "update"),
|
SelectFilterOption("Last Updated", "update"),
|
||||||
|
@ -470,6 +569,12 @@ open class BatoTo(
|
||||||
SelectFilterOption("Most Views 60 minutes", "views_h"),
|
SelectFilterOption("Most Views 60 minutes", "views_h"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getHistoryFilter() = listOf(
|
||||||
|
SelectFilterOption("None", ""),
|
||||||
|
SelectFilterOption("My History", "history"),
|
||||||
|
SelectFilterOption("My Updates", "updates"),
|
||||||
|
)
|
||||||
|
|
||||||
private fun getUtilsFilter() = listOf(
|
private fun getUtilsFilter() = listOf(
|
||||||
SelectFilterOption("None", ""),
|
SelectFilterOption("None", ""),
|
||||||
SelectFilterOption("Comics: I Created", "i-created"),
|
SelectFilterOption("Comics: I Created", "i-created"),
|
||||||
|
|
Loading…
Reference in New Issue