Initial commit
This commit is contained in:
53
src/all/batoto/AndroidManifest.xml
Normal file
53
src/all/batoto/AndroidManifest.xml
Normal 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
201
src/all/batoto/CHANGELOG.md
Normal 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
20
src/all/batoto/README.md
Normal 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 ( and ) with ( and )]: <> (- [Guides](#Guides))
|
||||
|
||||
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)
|
||||
17
src/all/batoto/build.gradle
Normal file
17
src/all/batoto/build.gradle
Normal 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'))
|
||||
}
|
||||
BIN
src/all/batoto/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/all/batoto/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/all/batoto/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/all/batoto/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/all/batoto/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/all/batoto/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/all/batoto/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/all/batoto/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
src/all/batoto/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/all/batoto/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
src/all/batoto/res/web_hi_res_512.png
Normal file
BIN
src/all/batoto/res/web_hi_res_512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user