Initial commit

This commit is contained in:
FourTOne5
2024-01-09 04:12:39 +06:00
commit 600c345dfe
8593 changed files with 150590 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.batoto.BatoToUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="*.bato.to" />
<data android:host="bato.to" />
<data android:host="*.batocc.com" />
<data android:host="batocc.com" />
<data android:host="*.batotoo.com" />
<data android:host="batotoo.com" />
<data android:host="*.batotwo.com" />
<data android:host="batotwo.com" />
<data android:host="*.battwo.com" />
<data android:host="battwo.com" />
<data android:host="*.comiko.net" />
<data android:host="comiko.net" />
<data android:host="*.mangatoto.com" />
<data android:host="mangatoto.com" />
<data android:host="*.mangatoto.net" />
<data android:host="mangatoto.net" />
<data android:host="*.mangatoto.org" />
<data android:host="mangatoto.org" />
<data android:host="*.mycordant.co.uk" />
<data android:host="mycordant.co.uk" />
<data android:host="*.dto.to" />
<data android:host="dto.to" />
<data android:host="*.hto.to" />
<data android:host="hto.to" />
<data android:host="*.mto.to" />
<data android:host="mto.to" />
<data android:host="*.wto.to" />
<data android:host="wto.to" />
<data
android:pathPattern="/series/..*"
android:scheme="https" />
<data
android:pathPattern="/subject-overview/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

201
src/all/batoto/CHANGELOG.md Normal file
View File

@@ -0,0 +1,201 @@
## 1.3.30
### Refactor
* Replace CryptoJS with Native Kotlin Functions
* Remove QuickJS dependency
## 1.3.29
### Refactor
* Cleanup pageListParse function
* Replace Duktape with QuickJS
## 1.3.28
### Features
* Add mirror `batocc.com`
* Add mirror `batotwo.com`
* Add mirror `mangatoto.net`
* Add mirror `mangatoto.org`
* Add mirror `mycordant.co.uk`
* Add mirror `dto.to`
* Add mirror `hto.to`
* Add mirror `mto.to`
* Add mirror `wto.to`
* Remove mirror `mycdhands.com`
## 1.3.27
### Features
* Change default popular sort by `Most Views Totally`
## 1.3.26
### Fix
* Update author and artist parsing
## 1.3.25
### Fix
* Status parsing
* Artist name parsing
## 1.3.24
### Fix
* Bump versions for individual extension with URL handler activities
## 1.2.23
### Fix
* Update pageListParse logic to handle website changes
## 1.2.22
### Features
* Add `CHANGELOG.md` & `README.md`
## 1.2.21
### Fix
* Update lang codes
## 1.2.20
### Features
* Rework of search
## 1.2.19
### Features
* Support for alternative chapter list
* Personal lists filter
## 1.2.18
### Features
* Utils lists filter
* Letter matching filter
## 1.2.17
### Features
* Add mirror `mycdhands.com`
## 1.2.16
### Features
* Mirror support
* URL intent updates
## 1.2.15
### Fix
* Manga description
## 1.2.14
### Features
* Escape entities
## 1.2.13
### Refactor
* Replace Gson with kotlinx.serialization
## 1.2.12
### Fix
* Infinity search
## 1.2.11
### Fix
* No search result
## 1.2.10
### Features
* Support for URL intent
* Updated filters
## 1.2.9
### Fix
* Chapter parsing
## 1.2.8
### Features
* More chapter filtering
## 1.2.7
### Fix
* Language filtering in latest
* Parsing of seconds
## 1.2.6
### Features
* Scanlator support
### Fix
* Date parsing
## 1.2.5
### Features
* Update supported Language list
## 1.2.4
### Features
* Support for excluding genres
## 1.2.3
### Fix
* Typo in some genres
## 1.2.2
### Features
* Reworked filter option
## 1.2.1
### Features
* Conversion from Emerald to Bato.to
* First version

20
src/all/batoto/README.md Normal file
View File

@@ -0,0 +1,20 @@
# Bato.to
Table of Content
- [FAQ](#FAQ)
- [Why are there Manga of diffrent languge than the selected one in Personal & Utils lists?](#why-are-there-manga-of-diffrent-languge-than-the-selected-one-in-personal--utils-lists)
- [Bato.to is not loading anything?](#batoto-is-not-loading-anything)
[Uncomment this if needed; and replace &#40; and &#41; with ( and )]: <> (- [Guides]&#40;#Guides&#41;)
Don't find the question you are look for go check out our general FAQs and Guides over at [Extension FAQ](https://tachiyomi.org/help/faq/#extensions) or [Getting Started](https://tachiyomi.org/help/guides/getting-started/#installation)
## FAQ
### Why are there Manga of diffrent languge than the selected one in Personal & Utils lists?
Personol & Utils lists have no way to difritiate between langueges.
### Bato.to is not loading anything?
Bato.to get blocked by some ISPs, try using a diffrent mirror of Bato.to from the settings.
[Uncomment this if needed]: <> (## Guides)

View File

@@ -0,0 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Bato.to'
pkgNameSuffix = 'all.batoto'
extClass = '.BatoToFactory'
extVersionCode = 33
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib-cryptoaes'))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -0,0 +1,974 @@
package eu.kanade.tachiyomi.extension.all.batoto
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.CheckBoxPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
import eu.kanade.tachiyomi.lib.cryptoaes.Deobfuscator
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.parser.Parser
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
open class BatoTo(
final override val lang: String,
private val siteLang: String,
) : ConfigurableSource, ParsedHttpSource() {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val name: String = "Bato.to"
override val baseUrl: String = getMirrorPref()!!
override val id: Long = when (lang) {
"zh-Hans" -> 2818874445640189582
"zh-Hant" -> 38886079663327225
"ro-MD" -> 8871355786189601023
else -> super.id
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val mirrorPref = ListPreference(screen.context).apply {
key = "${MIRROR_PREF_KEY}_$lang"
title = MIRROR_PREF_TITLE
entries = MIRROR_PREF_ENTRIES
entryValues = MIRROR_PREF_ENTRY_VALUES
setDefaultValue(MIRROR_PREF_DEFAULT_VALUE)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
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(altChapterListPref)
}
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)
override val supportsLatest = true
private val json: Json by injectLazy()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/browse?langs=$siteLang&sort=update&page=$page")
}
override fun latestUpdatesSelector(): String {
return when (siteLang) {
"" -> "div#series-list div.col"
"en" -> "div#series-list div.col.no-flag"
else -> "div#series-list div.col:has([data-lang=\"$siteLang\"])"
}
}
override fun latestUpdatesFromElement(element: Element): SManga {
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.title = element.select("a.item-title").text().removeEntities()
manga.thumbnail_url = imgurl
return manga
}
override fun latestUpdatesNextPageSelector() = "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)"
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/browse?langs=$siteLang&sort=views_a&page=$page")
}
override fun popularMangaSelector() = latestUpdatesSelector()
override fun popularMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun popularMangaNextPageSelector() = latestUpdatesNextPageSelector()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith("ID:") -> {
val id = query.substringAfter("ID:")
client.newCall(GET("$baseUrl/series/$id", headers)).asObservableSuccess()
.map { response ->
queryIDParse(response)
}
}
query.isNotBlank() -> {
val url = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("word", query)
.addQueryParameter("page", page.toString())
filters.forEach { filter ->
when (filter) {
is LetterFilter -> {
if (filter.state == 1) {
url.addQueryParameter("mode", "letter")
}
}
else -> { /* Do Nothing */ }
}
}
client.newCall(GET(url.build().toString(), headers)).asObservableSuccess()
.map { response ->
queryParse(response)
}
}
else -> {
val url = "$baseUrl/browse".toHttpUrlOrNull()!!.newBuilder()
var min = ""
var max = ""
filters.forEach { filter ->
when (filter) {
is UtilsFilter -> {
if (filter.state != 0) {
val filterUrl = "$baseUrl/_utils/comic-list?type=${filter.selected}"
return client.newCall(GET(filterUrl, headers)).asObservableSuccess()
.map { response ->
queryUtilsParse(response)
}
}
}
is HistoryFilter -> {
if (filter.state != 0) {
val filterUrl = "$baseUrl/ajax.my.${filter.selected}.paging"
return client.newCall(POST(filterUrl, headers, formBuilder().build())).asObservableSuccess()
.map { response ->
queryHistoryParse(response)
}
}
}
is LangGroupFilter -> {
if (filter.selected.isEmpty()) {
url.addQueryParameter("langs", siteLang)
} else {
val selection = "${filter.selected.joinToString(",")},$siteLang"
url.addQueryParameter("langs", selection)
}
}
is GenreGroupFilter -> {
with(filter) {
url.addQueryParameter(
"genres",
included.joinToString(",") + "|" + excluded.joinToString(","),
)
}
}
is StatusFilter -> url.addQueryParameter("release", filter.selected)
is SortFilter -> {
if (filter.state != null) {
val sort = getSortFilter()[filter.state!!.index].value
val value = when (filter.state!!.ascending) {
true -> "az"
false -> "za"
}
url.addQueryParameter("sort", "$sort.$value")
}
}
is OriginGroupFilter -> {
if (filter.selected.isNotEmpty()) {
url.addQueryParameter("origs", filter.selected.joinToString(","))
}
}
is MinChapterTextFilter -> min = filter.state
is MaxChapterTextFilter -> max = filter.state
else -> { /* Do Nothing */ }
}
}
url.addQueryParameter("page", page.toString())
if (max.isNotEmpty() or min.isNotEmpty()) {
url.addQueryParameter("chapters", "$min-$max")
}
client.newCall(GET(url.build().toString(), headers)).asObservableSuccess()
.map { response ->
queryParse(response)
}
}
}
}
private fun queryIDParse(response: Response): MangasPage {
val document = response.asJsoup()
val infoElement = document.select("div#mainer div.container-fluid")
val manga = SManga.create()
manga.title = infoElement.select("h3").text().removeEntities()
manga.thumbnail_url = document.select("div.attr-cover img")
.attr("abs:src")
manga.url = infoElement.select("h3 a").attr("abs:href")
return MangasPage(listOf(manga), false)
}
private fun queryParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector())
.map { element -> latestUpdatesFromElement(element) }
val nextPage = document.select(latestUpdatesNextPageSelector()).first() != null
return MangasPage(mangas, nextPage)
}
private fun queryUtilsParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select("tbody > tr")
.map { element -> searchUtilsFromElement(element) }
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 {
val manga = SManga.create()
manga.setUrlWithoutDomain(element.select("td a").attr("href"))
manga.title = element.select("td a").text()
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.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 searchMangaSelector() = throw UnsupportedOperationException("Not used")
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
override fun mangaDetailsRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
}
return super.mangaDetailsRequest(manga)
}
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#mainer div.container-fluid")
val manga = SManga.create()
val workStatus = infoElement.select("div.attr-item:contains(original work) span").text()
val uploadStatus = infoElement.select("div.attr-item:contains(upload status) span").text()
manga.title = infoElement.select("h3").text().removeEntities()
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 = infoElement.select("div.limit-html").text() + "\n" + infoElement.select(".episode-list > .alert-warning").text().trim()
manga.thumbnail_url = document.select("div.attr-cover img")
.attr("abs:src")
return manga
}
private fun parseStatus(workStatus: String?, uploadStatus: String?) = when {
workStatus == null -> SManga.UNKNOWN
workStatus.contains("Ongoing") -> SManga.ONGOING
workStatus.contains("Cancelled") -> SManga.CANCELLED
workStatus.contains("Hiatus") -> SManga.ON_HIATUS
workStatus.contains("Completed") -> when {
uploadStatus?.contains("Ongoing") == true -> SManga.PUBLISHING_FINISHED
else -> SManga.COMPLETED
}
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 {
if (manga.url.startsWith("http")) {
return GET(manga.url, headers)
}
return super.chapterListRequest(manga)
}
override fun chapterListSelector() = "div.main div.p-2"
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
val urlElement = element.select("a.chapt")
val group = element.select("div.extra > a:not(.ps-3)").text()
val time = element.select("div.extra > i.ps-3").text()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text()
if (group != "") {
chapter.scanlator = group
}
if (time != "") {
chapter.date_upload = parseChapterDate(time)
}
return chapter
}
private fun parseChapterDate(date: String): Long {
val value = date.split(' ')[0].toInt()
return when {
"secs" in date -> Calendar.getInstance().apply {
add(Calendar.SECOND, value * -1)
}.timeInMillis
"mins" in date -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
}.timeInMillis
"hours" in date -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
}.timeInMillis
"days" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
}.timeInMillis
"weeks" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
}.timeInMillis
"months" in date -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
}.timeInMillis
"years" in date -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
}.timeInMillis
"sec" in date -> Calendar.getInstance().apply {
add(Calendar.SECOND, value * -1)
}.timeInMillis
"min" in date -> Calendar.getInstance().apply {
add(Calendar.MINUTE, value * -1)
}.timeInMillis
"hour" in date -> Calendar.getInstance().apply {
add(Calendar.HOUR_OF_DAY, value * -1)
}.timeInMillis
"day" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * -1)
}.timeInMillis
"week" in date -> Calendar.getInstance().apply {
add(Calendar.DATE, value * 7 * -1)
}.timeInMillis
"month" in date -> Calendar.getInstance().apply {
add(Calendar.MONTH, value * -1)
}.timeInMillis
"year" in date -> Calendar.getInstance().apply {
add(Calendar.YEAR, value * -1)
}.timeInMillis
else -> {
return 0
}
}
}
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url.startsWith("http")) {
return GET(chapter.url, headers)
}
return super.pageListRequest(chapter)
}
override fun pageListParse(document: Document): List<Page> {
val script = document.selectFirst("script:containsData(imgHttps):containsData(batoWord):containsData(batoPass)")?.html()
?: throw RuntimeException("Couldn't find script with image data.")
val imgHttpLisString = script.substringAfter("const imgHttps =").substringBefore(";").trim()
val imgHttpLis = json.parseToJsonElement(imgHttpLisString).jsonArray.map { it.jsonPrimitive.content }
val batoWord = script.substringAfter("const batoWord =").substringBefore(";").trim()
val batoPass = script.substringAfter("const batoPass =").substringBefore(";").trim()
val evaluatedPass: String = Deobfuscator.deobfuscateJsPassword(batoPass)
val imgAccListString = CryptoAES.decrypt(batoWord.removeSurrounding("\""), evaluatedPass)
val imgAccList = json.parseToJsonElement(imgAccListString).jsonArray.map { it.jsonPrimitive.content }
return imgHttpLis.zip(imgAccList).mapIndexed { i, (imgUrl, imgAcc) ->
Page(i, imageUrl = "$imgUrl?$imgAcc")
}
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
private fun String.removeEntities(): String = Parser.unescapeEntities(this, true)
override fun getFilterList() = FilterList(
LetterFilter(getLetterFilter(), 0),
Filter.Separator(),
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
SortFilter(getSortFilter().map { it.name }.toTypedArray()),
StatusFilter(getStatusFilter(), 0),
GenreGroupFilter(getGenreFilter()),
OriginGroupFilter(getOrginFilter()),
LangGroupFilter(getLangFilter()),
MinChapterTextFilter(),
MaxChapterTextFilter(),
Filter.Separator(),
Filter.Header("NOTE: Filters below are incompatible with any other filters!"),
Filter.Header("NOTE: Login Required!"),
Filter.Separator(),
UtilsFilter(getUtilsFilter(), 0),
HistoryFilter(getHistoryFilter(), 0),
)
class SelectFilterOption(val name: String, val value: String)
class CheckboxFilterOption(val value: String, name: String, default: Boolean = false) : Filter.CheckBox(name, default)
class TriStateFilterOption(val value: String, name: String, default: Int = 0) : Filter.TriState(name, default)
abstract class SelectFilter(name: String, private val options: List<SelectFilterOption>, default: Int = 0) : Filter.Select<String>(name, options.map { it.name }.toTypedArray(), default) {
val selected: String
get() = options[state].value
}
abstract class CheckboxGroupFilter(name: String, options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>(name, options) {
val selected: List<String>
get() = state.filter { it.state }.map { it.value }
}
abstract class TriStateGroupFilter(name: String, options: List<TriStateFilterOption>) : Filter.Group<TriStateFilterOption>(name, options) {
val included: List<String>
get() = state.filter { it.isIncluded() }.map { it.value }
val excluded: List<String>
get() = state.filter { it.isExcluded() }.map { it.value }
}
abstract class TextFilter(name: String) : Filter.Text(name)
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort", sortables, Selection(5, false))
class StatusFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Status", options, default)
class OriginGroupFilter(options: List<CheckboxFilterOption>) : CheckboxGroupFilter("Origin", options)
class GenreGroupFilter(options: List<TriStateFilterOption>) : TriStateGroupFilter("Genre", options)
class MinChapterTextFilter : TextFilter("Min. Chapters")
class MaxChapterTextFilter : TextFilter("Max. Chapters")
class LangGroupFilter(options: List<CheckboxFilterOption>) : CheckboxGroupFilter("Languages", options)
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 HistoryFilter(options: List<SelectFilterOption>, default: Int) : SelectFilter("Personal list", options, default)
private fun getLetterFilter() = listOf(
SelectFilterOption("Disabled", "disabled"),
SelectFilterOption("Enabled", "enabled"),
)
private fun getSortFilter() = listOf(
SelectFilterOption("Z-A", "title"),
SelectFilterOption("Last Updated", "update"),
SelectFilterOption("Newest Added", "create"),
SelectFilterOption("Most Views Totally", "views_a"),
SelectFilterOption("Most Views 365 days", "views_y"),
SelectFilterOption("Most Views 30 days", "views_m"),
SelectFilterOption("Most Views 7 days", "views_w"),
SelectFilterOption("Most Views 24 hours", "views_d"),
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(
SelectFilterOption("None", ""),
SelectFilterOption("Comics: I Created", "i-created"),
SelectFilterOption("Comics: I Modified", "i-modified"),
SelectFilterOption("Comics: I Uploaded", "i-uploaded"),
SelectFilterOption("Comics: Authorized to me", "i-authorized"),
SelectFilterOption("Comics: Draft Status", "status-draft"),
SelectFilterOption("Comics: Hidden Status", "status-hidden"),
SelectFilterOption("Ongoing and Not updated in 30-60 days", "not-updated-30-60"),
SelectFilterOption("Ongoing and Not updated in 60-90 days", "not-updated-60-90"),
SelectFilterOption("Ongoing and Not updated in 90-180 days", "not-updated-90-180"),
SelectFilterOption("Ongoing and Not updated in 180-360 days", "not-updated-180-360"),
SelectFilterOption("Ongoing and Not updated in 360-1000 days", "not-updated-360-1000"),
SelectFilterOption("Ongoing and Not updated more than 1000 days", "not-updated-1000"),
)
private fun getStatusFilter() = listOf(
SelectFilterOption("All", ""),
SelectFilterOption("Pending", "pending"),
SelectFilterOption("Ongoing", "ongoing"),
SelectFilterOption("Completed", "completed"),
SelectFilterOption("Hiatus", "hiatus"),
SelectFilterOption("Cancelled", "cancelled"),
)
private fun getOrginFilter() = listOf(
// Values exported from publish.bato.to
CheckboxFilterOption("zh", "Chinese"),
CheckboxFilterOption("en", "English"),
CheckboxFilterOption("ja", "Japanese"),
CheckboxFilterOption("ko", "Korean"),
CheckboxFilterOption("af", "Afrikaans"),
CheckboxFilterOption("sq", "Albanian"),
CheckboxFilterOption("am", "Amharic"),
CheckboxFilterOption("ar", "Arabic"),
CheckboxFilterOption("hy", "Armenian"),
CheckboxFilterOption("az", "Azerbaijani"),
CheckboxFilterOption("be", "Belarusian"),
CheckboxFilterOption("bn", "Bengali"),
CheckboxFilterOption("bs", "Bosnian"),
CheckboxFilterOption("bg", "Bulgarian"),
CheckboxFilterOption("my", "Burmese"),
CheckboxFilterOption("km", "Cambodian"),
CheckboxFilterOption("ca", "Catalan"),
CheckboxFilterOption("ceb", "Cebuano"),
CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"),
CheckboxFilterOption("zh_tw", "Chinese (Traditional)"),
CheckboxFilterOption("hr", "Croatian"),
CheckboxFilterOption("cs", "Czech"),
CheckboxFilterOption("da", "Danish"),
CheckboxFilterOption("nl", "Dutch"),
CheckboxFilterOption("en_us", "English (United States)"),
CheckboxFilterOption("eo", "Esperanto"),
CheckboxFilterOption("et", "Estonian"),
CheckboxFilterOption("fo", "Faroese"),
CheckboxFilterOption("fil", "Filipino"),
CheckboxFilterOption("fi", "Finnish"),
CheckboxFilterOption("fr", "French"),
CheckboxFilterOption("ka", "Georgian"),
CheckboxFilterOption("de", "German"),
CheckboxFilterOption("el", "Greek"),
CheckboxFilterOption("gn", "Guarani"),
CheckboxFilterOption("gu", "Gujarati"),
CheckboxFilterOption("ht", "Haitian Creole"),
CheckboxFilterOption("ha", "Hausa"),
CheckboxFilterOption("he", "Hebrew"),
CheckboxFilterOption("hi", "Hindi"),
CheckboxFilterOption("hu", "Hungarian"),
CheckboxFilterOption("is", "Icelandic"),
CheckboxFilterOption("ig", "Igbo"),
CheckboxFilterOption("id", "Indonesian"),
CheckboxFilterOption("ga", "Irish"),
CheckboxFilterOption("it", "Italian"),
CheckboxFilterOption("jv", "Javanese"),
CheckboxFilterOption("kn", "Kannada"),
CheckboxFilterOption("kk", "Kazakh"),
CheckboxFilterOption("ku", "Kurdish"),
CheckboxFilterOption("ky", "Kyrgyz"),
CheckboxFilterOption("lo", "Laothian"),
CheckboxFilterOption("lv", "Latvian"),
CheckboxFilterOption("lt", "Lithuanian"),
CheckboxFilterOption("lb", "Luxembourgish"),
CheckboxFilterOption("mk", "Macedonian"),
CheckboxFilterOption("mg", "Malagasy"),
CheckboxFilterOption("ms", "Malay"),
CheckboxFilterOption("ml", "Malayalam"),
CheckboxFilterOption("mt", "Maltese"),
CheckboxFilterOption("mi", "Maori"),
CheckboxFilterOption("mr", "Marathi"),
CheckboxFilterOption("mo", "Moldavian"),
CheckboxFilterOption("mn", "Mongolian"),
CheckboxFilterOption("ne", "Nepali"),
CheckboxFilterOption("no", "Norwegian"),
CheckboxFilterOption("ny", "Nyanja"),
CheckboxFilterOption("ps", "Pashto"),
CheckboxFilterOption("fa", "Persian"),
CheckboxFilterOption("pl", "Polish"),
CheckboxFilterOption("pt", "Portuguese"),
CheckboxFilterOption("pt_br", "Portuguese (Brazil)"),
CheckboxFilterOption("ro", "Romanian"),
CheckboxFilterOption("rm", "Romansh"),
CheckboxFilterOption("ru", "Russian"),
CheckboxFilterOption("sm", "Samoan"),
CheckboxFilterOption("sr", "Serbian"),
CheckboxFilterOption("sh", "Serbo-Croatian"),
CheckboxFilterOption("st", "Sesotho"),
CheckboxFilterOption("sn", "Shona"),
CheckboxFilterOption("sd", "Sindhi"),
CheckboxFilterOption("si", "Sinhalese"),
CheckboxFilterOption("sk", "Slovak"),
CheckboxFilterOption("sl", "Slovenian"),
CheckboxFilterOption("so", "Somali"),
CheckboxFilterOption("es", "Spanish"),
CheckboxFilterOption("es_419", "Spanish (Latin America)"),
CheckboxFilterOption("sw", "Swahili"),
CheckboxFilterOption("sv", "Swedish"),
CheckboxFilterOption("tg", "Tajik"),
CheckboxFilterOption("ta", "Tamil"),
CheckboxFilterOption("th", "Thai"),
CheckboxFilterOption("ti", "Tigrinya"),
CheckboxFilterOption("to", "Tonga"),
CheckboxFilterOption("tr", "Turkish"),
CheckboxFilterOption("tk", "Turkmen"),
CheckboxFilterOption("uk", "Ukrainian"),
CheckboxFilterOption("ur", "Urdu"),
CheckboxFilterOption("uz", "Uzbek"),
CheckboxFilterOption("vi", "Vietnamese"),
CheckboxFilterOption("yo", "Yoruba"),
CheckboxFilterOption("zu", "Zulu"),
CheckboxFilterOption("_t", "Other"),
)
private fun getGenreFilter() = listOf(
TriStateFilterOption("artbook", "Artbook"),
TriStateFilterOption("cartoon", "Cartoon"),
TriStateFilterOption("comic", "Comic"),
TriStateFilterOption("doujinshi", "Doujinshi"),
TriStateFilterOption("imageset", "Imageset"),
TriStateFilterOption("manga", "Manga"),
TriStateFilterOption("manhua", "Manhua"),
TriStateFilterOption("manhwa", "Manhwa"),
TriStateFilterOption("webtoon", "Webtoon"),
TriStateFilterOption("western", "Western"),
TriStateFilterOption("shoujo", "Shoujo(G)"),
TriStateFilterOption("shounen", "Shounen(B)"),
TriStateFilterOption("josei", "Josei(W)"),
TriStateFilterOption("seinen", "Seinen(M)"),
TriStateFilterOption("yuri", "Yuri(GL)"),
TriStateFilterOption("yaoi", "Yaoi(BL)"),
TriStateFilterOption("futa", "Futa(WL)"),
TriStateFilterOption("bara", "Bara(ML)"),
TriStateFilterOption("gore", "Gore"),
TriStateFilterOption("bloody", "Bloody"),
TriStateFilterOption("violence", "Violence"),
TriStateFilterOption("ecchi", "Ecchi"),
TriStateFilterOption("adult", "Adult"),
TriStateFilterOption("mature", "Mature"),
TriStateFilterOption("smut", "Smut"),
TriStateFilterOption("hentai", "Hentai"),
TriStateFilterOption("_4_koma", "4-Koma"),
TriStateFilterOption("action", "Action"),
TriStateFilterOption("adaptation", "Adaptation"),
TriStateFilterOption("adventure", "Adventure"),
TriStateFilterOption("age_gap", "Age Gap"),
TriStateFilterOption("aliens", "Aliens"),
TriStateFilterOption("animals", "Animals"),
TriStateFilterOption("anthology", "Anthology"),
TriStateFilterOption("beasts", "Beasts"),
TriStateFilterOption("bodyswap", "Bodyswap"),
TriStateFilterOption("cars", "cars"),
TriStateFilterOption("cheating_infidelity", "Cheating/Infidelity"),
TriStateFilterOption("childhood_friends", "Childhood Friends"),
TriStateFilterOption("college_life", "College Life"),
TriStateFilterOption("comedy", "Comedy"),
TriStateFilterOption("contest_winning", "Contest Winning"),
TriStateFilterOption("cooking", "Cooking"),
TriStateFilterOption("crime", "crime"),
TriStateFilterOption("crossdressing", "Crossdressing"),
TriStateFilterOption("delinquents", "Delinquents"),
TriStateFilterOption("dementia", "Dementia"),
TriStateFilterOption("demons", "Demons"),
TriStateFilterOption("drama", "Drama"),
TriStateFilterOption("dungeons", "Dungeons"),
TriStateFilterOption("emperor_daughte", "Emperor's Daughter"),
TriStateFilterOption("fantasy", "Fantasy"),
TriStateFilterOption("fan_colored", "Fan-Colored"),
TriStateFilterOption("fetish", "Fetish"),
TriStateFilterOption("full_color", "Full Color"),
TriStateFilterOption("game", "Game"),
TriStateFilterOption("gender_bender", "Gender Bender"),
TriStateFilterOption("genderswap", "Genderswap"),
TriStateFilterOption("ghosts", "Ghosts"),
TriStateFilterOption("gyaru", "Gyaru"),
TriStateFilterOption("harem", "Harem"),
TriStateFilterOption("harlequin", "Harlequin"),
TriStateFilterOption("historical", "Historical"),
TriStateFilterOption("horror", "Horror"),
TriStateFilterOption("incest", "Incest"),
TriStateFilterOption("isekai", "Isekai"),
TriStateFilterOption("kids", "Kids"),
TriStateFilterOption("loli", "Loli"),
TriStateFilterOption("magic", "Magic"),
TriStateFilterOption("magical_girls", "Magical Girls"),
TriStateFilterOption("martial_arts", "Martial Arts"),
TriStateFilterOption("mecha", "Mecha"),
TriStateFilterOption("medical", "Medical"),
TriStateFilterOption("military", "Military"),
TriStateFilterOption("monster_girls", "Monster Girls"),
TriStateFilterOption("monsters", "Monsters"),
TriStateFilterOption("music", "Music"),
TriStateFilterOption("mystery", "Mystery"),
TriStateFilterOption("netorare", "Netorare/NTR"),
TriStateFilterOption("ninja", "Ninja"),
TriStateFilterOption("office_workers", "Office Workers"),
TriStateFilterOption("omegaverse", "Omegaverse"),
TriStateFilterOption("oneshot", "Oneshot"),
TriStateFilterOption("parody", "parody"),
TriStateFilterOption("philosophical", "Philosophical"),
TriStateFilterOption("police", "Police"),
TriStateFilterOption("post_apocalyptic", "Post-Apocalyptic"),
TriStateFilterOption("psychological", "Psychological"),
TriStateFilterOption("regression", "Regression"),
TriStateFilterOption("reincarnation", "Reincarnation"),
TriStateFilterOption("reverse_harem", "Reverse Harem"),
TriStateFilterOption("reverse_isekai", "Reverse Isekai"),
TriStateFilterOption("romance", "Romance"),
TriStateFilterOption("royal_family", "Royal Family"),
TriStateFilterOption("royalty", "Royalty"),
TriStateFilterOption("samurai", "Samurai"),
TriStateFilterOption("school_life", "School Life"),
TriStateFilterOption("sci_fi", "Sci-Fi"),
TriStateFilterOption("shota", "Shota"),
TriStateFilterOption("shoujo_ai", "Shoujo Ai"),
TriStateFilterOption("shounen_ai", "Shounen Ai"),
TriStateFilterOption("showbiz", "Showbiz"),
TriStateFilterOption("slice_of_life", "Slice of Life"),
TriStateFilterOption("sm_bdsm", "SM/BDSM/SUB-DOM"),
TriStateFilterOption("space", "Space"),
TriStateFilterOption("sports", "Sports"),
TriStateFilterOption("super_power", "Super Power"),
TriStateFilterOption("superhero", "Superhero"),
TriStateFilterOption("supernatural", "Supernatural"),
TriStateFilterOption("survival", "Survival"),
TriStateFilterOption("thriller", "Thriller"),
TriStateFilterOption("time_travel", "Time Travel"),
TriStateFilterOption("tower_climbing", "Tower Climbing"),
TriStateFilterOption("traditional_games", "Traditional Games"),
TriStateFilterOption("tragedy", "Tragedy"),
TriStateFilterOption("transmigration", "Transmigration"),
TriStateFilterOption("vampires", "Vampires"),
TriStateFilterOption("villainess", "Villainess"),
TriStateFilterOption("video_games", "Video Games"),
TriStateFilterOption("virtual_reality", "Virtual Reality"),
TriStateFilterOption("wuxia", "Wuxia"),
TriStateFilterOption("xianxia", "Xianxia"),
TriStateFilterOption("xuanhuan", "Xuanhuan"),
TriStateFilterOption("zombies", "Zombies"),
// Hidden Genres
TriStateFilterOption("shotacon", "shotacon"),
TriStateFilterOption("lolicon", "lolicon"),
TriStateFilterOption("award_winning", "Award Winning"),
TriStateFilterOption("youkai", "Youkai"),
TriStateFilterOption("uncategorized", "Uncategorized"),
)
private fun getLangFilter() = listOf(
// Values exported from publish.bato.to
CheckboxFilterOption("en", "English"),
CheckboxFilterOption("ar", "Arabic"),
CheckboxFilterOption("bg", "Bulgarian"),
CheckboxFilterOption("zh", "Chinese"),
CheckboxFilterOption("cs", "Czech"),
CheckboxFilterOption("da", "Danish"),
CheckboxFilterOption("nl", "Dutch"),
CheckboxFilterOption("fil", "Filipino"),
CheckboxFilterOption("fi", "Finnish"),
CheckboxFilterOption("fr", "French"),
CheckboxFilterOption("de", "German"),
CheckboxFilterOption("el", "Greek"),
CheckboxFilterOption("he", "Hebrew"),
CheckboxFilterOption("hi", "Hindi"),
CheckboxFilterOption("hu", "Hungarian"),
CheckboxFilterOption("id", "Indonesian"),
CheckboxFilterOption("it", "Italian"),
CheckboxFilterOption("ja", "Japanese"),
CheckboxFilterOption("ko", "Korean"),
CheckboxFilterOption("ms", "Malay"),
CheckboxFilterOption("pl", "Polish"),
CheckboxFilterOption("pt", "Portuguese"),
CheckboxFilterOption("pt_br", "Portuguese (Brazil)"),
CheckboxFilterOption("ro", "Romanian"),
CheckboxFilterOption("ru", "Russian"),
CheckboxFilterOption("es", "Spanish"),
CheckboxFilterOption("es_419", "Spanish (Latin America)"),
CheckboxFilterOption("sv", "Swedish"),
CheckboxFilterOption("th", "Thai"),
CheckboxFilterOption("tr", "Turkish"),
CheckboxFilterOption("uk", "Ukrainian"),
CheckboxFilterOption("vi", "Vietnamese"),
CheckboxFilterOption("af", "Afrikaans"),
CheckboxFilterOption("sq", "Albanian"),
CheckboxFilterOption("am", "Amharic"),
CheckboxFilterOption("hy", "Armenian"),
CheckboxFilterOption("az", "Azerbaijani"),
CheckboxFilterOption("be", "Belarusian"),
CheckboxFilterOption("bn", "Bengali"),
CheckboxFilterOption("bs", "Bosnian"),
CheckboxFilterOption("my", "Burmese"),
CheckboxFilterOption("km", "Cambodian"),
CheckboxFilterOption("ca", "Catalan"),
CheckboxFilterOption("ceb", "Cebuano"),
CheckboxFilterOption("zh_hk", "Chinese (Cantonese)"),
CheckboxFilterOption("zh_tw", "Chinese (Traditional)"),
CheckboxFilterOption("hr", "Croatian"),
CheckboxFilterOption("en_us", "English (United States)"),
CheckboxFilterOption("eo", "Esperanto"),
CheckboxFilterOption("et", "Estonian"),
CheckboxFilterOption("fo", "Faroese"),
CheckboxFilterOption("ka", "Georgian"),
CheckboxFilterOption("gn", "Guarani"),
CheckboxFilterOption("gu", "Gujarati"),
CheckboxFilterOption("ht", "Haitian Creole"),
CheckboxFilterOption("ha", "Hausa"),
CheckboxFilterOption("is", "Icelandic"),
CheckboxFilterOption("ig", "Igbo"),
CheckboxFilterOption("ga", "Irish"),
CheckboxFilterOption("jv", "Javanese"),
CheckboxFilterOption("kn", "Kannada"),
CheckboxFilterOption("kk", "Kazakh"),
CheckboxFilterOption("ku", "Kurdish"),
CheckboxFilterOption("ky", "Kyrgyz"),
CheckboxFilterOption("lo", "Laothian"),
CheckboxFilterOption("lv", "Latvian"),
CheckboxFilterOption("lt", "Lithuanian"),
CheckboxFilterOption("lb", "Luxembourgish"),
CheckboxFilterOption("mk", "Macedonian"),
CheckboxFilterOption("mg", "Malagasy"),
CheckboxFilterOption("ml", "Malayalam"),
CheckboxFilterOption("mt", "Maltese"),
CheckboxFilterOption("mi", "Maori"),
CheckboxFilterOption("mr", "Marathi"),
CheckboxFilterOption("mo", "Moldavian"),
CheckboxFilterOption("mn", "Mongolian"),
CheckboxFilterOption("ne", "Nepali"),
CheckboxFilterOption("no", "Norwegian"),
CheckboxFilterOption("ny", "Nyanja"),
CheckboxFilterOption("ps", "Pashto"),
CheckboxFilterOption("fa", "Persian"),
CheckboxFilterOption("rm", "Romansh"),
CheckboxFilterOption("sm", "Samoan"),
CheckboxFilterOption("sr", "Serbian"),
CheckboxFilterOption("sh", "Serbo-Croatian"),
CheckboxFilterOption("st", "Sesotho"),
CheckboxFilterOption("sn", "Shona"),
CheckboxFilterOption("sd", "Sindhi"),
CheckboxFilterOption("si", "Sinhalese"),
CheckboxFilterOption("sk", "Slovak"),
CheckboxFilterOption("sl", "Slovenian"),
CheckboxFilterOption("so", "Somali"),
CheckboxFilterOption("sw", "Swahili"),
CheckboxFilterOption("tg", "Tajik"),
CheckboxFilterOption("ta", "Tamil"),
CheckboxFilterOption("ti", "Tigrinya"),
CheckboxFilterOption("to", "Tonga"),
CheckboxFilterOption("tk", "Turkmen"),
CheckboxFilterOption("ur", "Urdu"),
CheckboxFilterOption("uz", "Uzbek"),
CheckboxFilterOption("yo", "Yoruba"),
CheckboxFilterOption("zu", "Zulu"),
CheckboxFilterOption("_t", "Other"),
// Lang options from bato.to brows not in publish.bato.to
CheckboxFilterOption("eu", "Basque"),
CheckboxFilterOption("pt-PT", "Portuguese (Portugal)"),
).filterNot { it.value == siteLang }
companion object {
private const val MIRROR_PREF_KEY = "MIRROR"
private const val MIRROR_PREF_TITLE = "Mirror"
private val MIRROR_PREF_ENTRIES = arrayOf(
"bato.to",
"batocomic.com",
"batocomic.net",
"batocomic.org",
"batotoo.com",
"batotwo.com",
"battwo.com",
"comiko.net",
"comiko.org",
"mangatoto.com",
"mangatoto.net",
"mangatoto.org",
"readtoto.com",
"readtoto.net",
"readtoto.org",
"dto.to",
"hto.to",
"mto.to",
"wto.to",
"xbato.com",
"xbato.net",
"xbato.org",
"zbato.com",
"zbato.net",
"zbato.org",
)
private val MIRROR_PREF_ENTRY_VALUES = MIRROR_PREF_ENTRIES.map { "https://$it" }.toTypedArray()
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
}
}

View File

@@ -0,0 +1,122 @@
package eu.kanade.tachiyomi.extension.all.batoto
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class BatoToFactory : SourceFactory {
override fun createSources(): List<Source> = languages.map { BatoTo(it.lang, it.siteLang) }
}
class LanguageOption(val lang: String, val siteLang: String = lang)
private val languages = listOf(
LanguageOption("all", ""),
// Lang options from publish.bato.to
LanguageOption("en"),
LanguageOption("ar"),
LanguageOption("bg"),
LanguageOption("zh"),
LanguageOption("cs"),
LanguageOption("da"),
LanguageOption("nl"),
LanguageOption("fil"),
LanguageOption("fi"),
LanguageOption("fr"),
LanguageOption("de"),
LanguageOption("el"),
LanguageOption("he"),
LanguageOption("hi"),
LanguageOption("hu"),
LanguageOption("id"),
LanguageOption("it"),
LanguageOption("ja"),
LanguageOption("ko"),
LanguageOption("ms"),
LanguageOption("pl"),
LanguageOption("pt"),
LanguageOption("pt-BR", "pt_br"),
LanguageOption("ro"),
LanguageOption("ru"),
LanguageOption("es"),
LanguageOption("es-419", "es_419"),
LanguageOption("sv"),
LanguageOption("th"),
LanguageOption("tr"),
LanguageOption("uk"),
LanguageOption("vi"),
LanguageOption("af"),
LanguageOption("sq"),
LanguageOption("am"),
LanguageOption("hy"),
LanguageOption("az"),
LanguageOption("be"),
LanguageOption("bn"),
LanguageOption("bs"),
LanguageOption("my"),
LanguageOption("km"),
LanguageOption("ca"),
LanguageOption("ceb"),
LanguageOption("zh-Hans", "zh_hk"),
LanguageOption("zh-Hant", "zh_tw"),
LanguageOption("hr"),
LanguageOption("en-US", "en_us"),
LanguageOption("eo"),
LanguageOption("et"),
LanguageOption("fo"),
LanguageOption("ka"),
LanguageOption("gn"),
LanguageOption("gu"),
LanguageOption("ht"),
LanguageOption("ha"),
LanguageOption("is"),
LanguageOption("ig"),
LanguageOption("ga"),
LanguageOption("jv"),
LanguageOption("kn"),
LanguageOption("kk"),
LanguageOption("ku"),
LanguageOption("ky"),
LanguageOption("lo"),
LanguageOption("lv"),
LanguageOption("lt"),
LanguageOption("lb"),
LanguageOption("mk"),
LanguageOption("mg"),
LanguageOption("ml"),
LanguageOption("mt"),
LanguageOption("mi"),
LanguageOption("mr"),
LanguageOption("mo", "ro-MD"),
LanguageOption("mn"),
LanguageOption("ne"),
LanguageOption("no"),
LanguageOption("ny"),
LanguageOption("ps"),
LanguageOption("fa"),
LanguageOption("rm"),
LanguageOption("sm"),
LanguageOption("sr"),
LanguageOption("sh"),
LanguageOption("st"),
LanguageOption("sn"),
LanguageOption("sd"),
LanguageOption("si"),
LanguageOption("sk"),
LanguageOption("sl"),
LanguageOption("so"),
LanguageOption("sw"),
LanguageOption("tg"),
LanguageOption("ta"),
LanguageOption("ti"),
LanguageOption("to"),
LanguageOption("tk"),
LanguageOption("ur"),
LanguageOption("uz"),
LanguageOption("yo"),
LanguageOption("zu"),
LanguageOption("other", "_t"),
// Lang options from bato.to brows not in publish.bato.to
LanguageOption("eu"),
LanguageOption("pt-PT", "pt_pt"),
// Lang options that got removed
// Pair("xh", "xh"),
)

View File

@@ -0,0 +1,51 @@
package eu.kanade.tachiyomi.extension.all.batoto
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class BatoToUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val host = intent?.data?.host
val pathSegments = intent?.data?.pathSegments
if (host != null && pathSegments != null) {
val query = fromBatoTo(pathSegments)
if (query == null) {
Log.e("BatoToUrlActivity", "Unable to parse URI from intent $intent")
finish()
exitProcess(1)
}
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", query)
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("BatoToUrlActivity", e.toString())
}
}
finish()
exitProcess(0)
}
private fun fromBatoTo(pathSegments: MutableList<String>): String? {
return if (pathSegments.size >= 2) {
val id = pathSegments[1]
"ID:$id"
} else {
null
}
}
}