Update TMO, LectorTMO & TMOHentai (#6307)

* Add configurable rate limit, image CDN rate limit and SFW mode.

* Add configurable image download mode (cascade/paginated).

* Configurable ratelimit and image CDN ratelimit.

* Fix redundant title
This commit is contained in:
Edgar Mejía 2021-03-28 05:13:45 -06:00 committed by GitHub
parent 59b380205f
commit 135e08b754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 593 additions and 239 deletions

View File

@ -5,8 +5,12 @@ ext {
extName = 'LectorManga' extName = 'LectorManga'
pkgNameSuffix = 'es.lectormanga' pkgNameSuffix = 'es.lectormanga'
extClass = '.LectorManga' extClass = '.LectorManga'
extVersionCode = 14 extVersionCode = 15
libVersion = '1.2' libVersion = '1.2'
} }
dependencies {
implementation project(':lib-ratelimit')
}
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.extension.es.lectormanga
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.CheckBoxPreference
import android.support.v7.preference.ListPreference import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
@ -15,8 +17,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -27,9 +29,6 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
/**
* Note: this source is similar to TuMangaOnline.
*/
class LectorManga : ConfigurableSource, ParsedHttpSource() { class LectorManga : ConfigurableSource, ParsedHttpSource() {
override val name = "LectorManga" override val name = "LectorManga"
@ -40,14 +39,29 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" private val imageCDNUrl = "https://img1.followmanga.com"
override fun headersBuilder(): Headers.Builder { private val preferences: SharedPreferences by lazy {
return Headers.Builder() Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
.add("User-Agent", userAgent)
.add("Referer", "$baseUrl/")
} }
private val webRateLimitInterceptor = SpecificHostRateLimitInterceptor(
HttpUrl.parse(baseUrl)!!,
preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
60
)
private val imageCDNRateLimitInterceptor = SpecificHostRateLimitInterceptor(
HttpUrl.parse(imageCDNUrl)!!,
preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
60
)
override val client: OkHttpClient = network.client.newBuilder()
.addNetworkInterceptor(webRateLimitInterceptor)
.addNetworkInterceptor(imageCDNRateLimitInterceptor)
.build()
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&type=&filter_by=title&page=$page", headers) override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&type=&filter_by=title&page=$page", headers)
override fun popularMangaNextPageSelector() = ".pagination .page-item:not(.disabled) a[rel='next']" override fun popularMangaNextPageSelector() = ".pagination .page-item:not(.disabled) a[rel='next']"
@ -176,17 +190,16 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
} }
// Regular list of chapters // Regular list of chapters
val dupselect = getduppref()!!
val chapterNames = document.select("#chapters h4.text-truncate") val chapterNames = document.select("#chapters h4.text-truncate")
val chapterNumbers = chapterNames.map { it.text().substringAfter("Capítulo").substringBefore("|").trim().toFloat() } val chapterNumbers = chapterNames.map { it.text().substringAfter("Capítulo").substringBefore("|").trim().toFloat() }
val chapterInfos = document.select("#chapters .chapter-list") val chapterInfos = document.select("#chapters .chapter-list")
chapterNames.forEachIndexed { index, _ -> chapterNames.forEachIndexed { index, _ ->
val scanlator = chapterInfos[index].select("li") val scanlator = chapterInfos[index].select("li")
if (dupselect == "one") { if (getScanlatorPref()) {
scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) }
} else {
scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) } scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) }
} else {
scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) }
} }
} }
} }
@ -224,9 +237,9 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri() val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri()
// Get /cascade instead of /paginate to get all pages at once // Get /cascade instead of /paginate to get all pages at once
val newUrl = if (getPageMethod() == "cascade" && currentUrl.contains("paginated")) { val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) {
currentUrl.substringBefore("paginated") + "cascade" currentUrl.substringBefore("paginated") + "cascade"
} else if (getPageMethod() == "paginated" && currentUrl.contains("cascade")) { } else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) {
currentUrl.substringBefore("cascade") + "paginated" currentUrl.substringBefore("cascade") + "paginated"
} else currentUrl } else currentUrl
@ -234,7 +247,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
} }
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
if (getPageMethod() == "cascade") { if (getPageMethodPref() == "cascade") {
document.select("div.viewer-container img").forEach { document.select("div.viewer-container img").forEach {
add( add(
Page( Page(
@ -410,91 +423,192 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
// Preferences
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val deduppref = androidx.preference.ListPreference(screen.context).apply {
key = DEDUP_PREF_Title val scanlatorPref = androidx.preference.CheckBoxPreference(screen.context).apply {
title = DEDUP_PREF_Title key = SCANLATOR_PREF
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator") title = SCANLATOR_PREF_TITLE
entryValues = arrayOf("all", "one") summary = SCANLATOR_PREF_SUMMARY
summary = "%s" setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val checkValue = newValue as Boolean
val index = this.findIndexOfValue(selected) preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit()
val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
val pageMethod = androidx.preference.ListPreference(screen.context).apply { val pageMethodPref = androidx.preference.ListPreference(screen.context).apply {
key = PAGEGET_PREF_Title key = PAGE_METHOD_PREF
title = PAGEGET_PREF_Title title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada (recomendado)", "Paginado") entries = arrayOf("Cascada", "ginado")
entryValues = arrayOf("cascade", "paginated") entryValues = arrayOf("cascade", "paginated")
summary = "%s" summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String try {
val index = this.findIndexOfValue(selected) val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
val entry = entryValues[index] as String setting
preferences.edit().putString(PAGEGET_PREF, entry).commit() } catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
screen.addPreference(deduppref) // Rate limit
screen.addPreference(pageMethod) val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply {
key = WEB_RATELIMIT_PREF
title = WEB_RATELIMIT_PREF_TITLE
summary = WEB_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply {
key = IMAGE_CDN_RATELIMIT_PREF
title = IMAGE_CDN_RATELIMIT_PREF_TITLE
summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(scanlatorPref)
screen.addPreference(pageMethodPref)
screen.addPreference(apiRateLimitPreference)
screen.addPreference(imgCDNRateLimitPreference)
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val deduppref = ListPreference(screen.context).apply {
key = DEDUP_PREF_Title val scanlatorPref = CheckBoxPreference(screen.context).apply {
title = DEDUP_PREF_Title key = SCANLATOR_PREF
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator") title = SCANLATOR_PREF_TITLE
entryValues = arrayOf("all", "one") summary = SCANLATOR_PREF_SUMMARY
summary = "%s" setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val checkValue = newValue as Boolean
val index = this.findIndexOfValue(selected) preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit()
val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
val pageMethod = ListPreference(screen.context).apply { val pageMethodPref = ListPreference(screen.context).apply {
key = PAGEGET_PREF_Title key = PAGE_METHOD_PREF
title = PAGEGET_PREF_Title title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada (recomendado)", "Paginado") entries = arrayOf("Cascada", "ginado")
entryValues = arrayOf("cascade", "paginated") entryValues = arrayOf("cascade", "paginated")
summary = "%s" summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String try {
val index = this.findIndexOfValue(selected) val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
val entry = entryValues[index] as String setting
preferences.edit().putString(PAGEGET_PREF, entry).commit() } catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
screen.addPreference(deduppref) // Rate limit
screen.addPreference(pageMethod) val apiRateLimitPreference = ListPreference(screen.context).apply {
key = WEB_RATELIMIT_PREF
title = WEB_RATELIMIT_PREF_TITLE
summary = WEB_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
val imgCDNRateLimitPreference = ListPreference(screen.context).apply {
key = IMAGE_CDN_RATELIMIT_PREF
title = IMAGE_CDN_RATELIMIT_PREF_TITLE
summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(scanlatorPref)
screen.addPreference(pageMethodPref)
screen.addPreference(apiRateLimitPreference)
screen.addPreference(imgCDNRateLimitPreference)
} }
private fun getduppref() = preferences.getString(DEDUP_PREF, "all") private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE)
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade") private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE)
companion object { companion object {
private const val DEDUP_PREF_Title = "Preferencias de scanlator" private const val SCANLATOR_PREF = "scanlatorPref"
private const val DEDUP_PREF = "deduppref" private const val SCANLATOR_PREF_TITLE = "Mostrar todos los scanlator"
private const val PAGEGET_PREF_Title = "Método para la descarga de imágenes" private const val SCANLATOR_PREF_SUMMARY = "Se mostraran capítulos repetidos pero con diferentes Scanlators"
private const val PAGEGET_PREF = "pagemethodpref" private const val SCANLATOR_PREF_DEFAULT_VALUE = true
private const val PAGE_METHOD_PREF = "pageMethodPref"
private const val PAGE_METHOD_PREF_TITLE = "Método para descargar imágenes"
private const val PAGE_METHOD_PREF_SUMMARY = "Previene ser banneado por el servidor cuando se usa la configuración \"Cascada\" ya que esta reduce la cantidad de solicitudes.\nPuedes usar \"Páginado\" cuando las imágenes no carguen usando \"Cascada\".\nConfiguración actual: %s"
private const val PAGE_METHOD_PREF_DEFAULT_VALUE = "cascade"
private const val WEB_RATELIMIT_PREF = "webRatelimitPreference"
// Ratelimit permits per second for main website
private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web"
// This value affects network request amount to TMO url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s
private const val WEB_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red a la URL de TMO. Reducir este valor puede disminuir la posibilidad de obtener un error HTTP 429, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s"
private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "10"
private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference"
// Ratelimit permits per second for image CDN
private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes"
// This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s
private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red para descargar imágenes. Reducir este valor puede disminuir errores al cargar imagenes, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s"
private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "10"
private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray()
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"
const val MANGA_URL_CHUNK = "gotobook" const val MANGA_URL_CHUNK = "gotobook"

View File

@ -5,7 +5,7 @@ ext {
extName = 'TMOHentai' extName = 'TMOHentai'
pkgNameSuffix = 'es.tmohentai' pkgNameSuffix = 'es.tmohentai'
extClass = '.TMOHentai' extClass = '.TMOHentai'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.extension.es.tmohentai package eu.kanade.tachiyomi.extension.es.tmohentai
import android.app.Application
import android.content.SharedPreferences
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess 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.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@ -15,9 +20,11 @@ import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Nsfw @Nsfw
class TMOHentai : ParsedHttpSource() { class TMOHentai : ConfigurableSource, ParsedHttpSource() {
override val name = "TMOHentai" override val name = "TMOHentai"
@ -27,6 +34,10 @@ class TMOHentai : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=popularity&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers) override fun popularMangaRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=popularity&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers)
override fun popularMangaSelector() = "table > tbody > tr[data-toggle=popover]" override fun popularMangaSelector() = "table > tbody > tr[data-toggle=popover]"
@ -71,19 +82,47 @@ class TMOHentai : ParsedHttpSource() {
name = element.select("h3.truncate").text() name = element.select("h3.truncate").text()
scanlator = parsedInformation.substringAfter("By").substringBefore("Language").trim() scanlator = parsedInformation.substringAfter("By").substringBefore("Language").trim()
setUrlWithoutDomain(element.select("a.pull-right.btn.btn-primary").attr("href"))
var currentUrl = element.select("a.pull-right.btn.btn-primary").attr("href")
if (currentUrl.contains("/1")) {
currentUrl = currentUrl.substringBeforeLast("/")
}
setUrlWithoutDomain(currentUrl)
// date_upload = no date in the web // date_upload = no date in the web
} }
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url.substringBefore("/paginated") + "/cascade", headers) // "/cascade" to get all images // "/cascade" to get all images
override fun pageListRequest(chapter: SChapter): Request {
val currentUrl = chapter.url
val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) {
currentUrl.substringBefore("paginated") + "cascade"
} else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) {
currentUrl.substringBefore("cascade") + "paginated"
} else currentUrl
return GET("$baseUrl$newUrl", headers)
}
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
document.select("div#content-images > div.row > div.col-xs-12.text-center > img.content-image")?.forEach { if (getPageMethodPref() == "cascade") {
add(Page(size, "", baseUrl + it.attr("data-original"))) document.select("div#content-images img.content-image")?.forEach {
add(Page(size, "", it.attr("data-original")))
}
} else {
val pageList = document.select("select#select-page").first().select("option").map { it.attr("value").toInt() }
val url = document.baseUri()
pageList.forEach {
add(Page(it, "$url/$it"))
}
} }
} }
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") override fun imageUrlParse(document: Document) = document.select("div#content-images img.content-image").attr("data-original")
override fun imageRequest(page: Page) = GET("$baseUrl${page.imageUrl!!}", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/section/all?view=list")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/section/all?view=list")!!.newBuilder()
@ -193,9 +232,13 @@ class TMOHentai : ParsedHttpSource() {
Selection(2, false) Selection(2, false)
) )
// Array.from(document.querySelectorAll('#advancedSearch .list-group .list-group-item')) /**
// .map(a => `Genre("${a.querySelector('span').innerText.replace(' ', '')}", "${a.querySelector('input').value}")`).join(',\n') * Last check: 17/02/2021
// https://tmohentai.com/section/hentai * https://tmohentai.com/section/hentai
*
* Array.from(document.querySelectorAll('#advancedSearch .list-group .list-group-item'))
* .map(a => `Genre("${a.querySelector('span').innerText.replace(' ', '')}", "${a.querySelector('input').value}")`).join(',\n')
*/
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Romance", "1"), Genre("Romance", "1"),
Genre("Fantasy", "2"), Genre("Fantasy", "2"),
@ -245,7 +288,62 @@ class TMOHentai : ParsedHttpSource() {
Genre("Yandere", "46") Genre("Yandere", "46")
) )
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val pageMethodPref = androidx.preference.ListPreference(screen.context).apply {
key = PAGE_METHOD_PREF
title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada", "Páginado")
entryValues = arrayOf("cascade", "paginated")
summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(pageMethodPref)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val pageMethodPref = ListPreference(screen.context).apply {
key = PAGE_METHOD_PREF
title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada", "Páginado")
entryValues = arrayOf(PAGE_METHOD_PREF_CASCADE, PAGE_METHOD_PREF_PAGINATED)
summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(pageMethodPref)
}
private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE)
companion object { companion object {
private const val PAGE_METHOD_PREF = "pageMethodPref"
private const val PAGE_METHOD_PREF_TITLE = "Método de descarga de imágenes"
private const val PAGE_METHOD_PREF_SUMMARY = "Puede corregir errores al cargar las imágenes.\nConfiguración actual: %s"
private const val PAGE_METHOD_PREF_CASCADE = "cascade"
private const val PAGE_METHOD_PREF_PAGINATED = "paginated"
private const val PAGE_METHOD_PREF_DEFAULT_VALUE = PAGE_METHOD_PREF_CASCADE
const val PREFIX_CONTENTS = "contents" const val PREFIX_CONTENTS = "contents"
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"

View File

@ -5,7 +5,7 @@ ext {
extName = 'TuMangaOnline' extName = 'TuMangaOnline'
pkgNameSuffix = 'es.tumangaonline' pkgNameSuffix = 'es.tumangaonline'
extClass = '.TuMangaOnline' extClass = '.TuMangaOnline'
extVersionCode = 31 extVersionCode = 32
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension.es.tumangaonline
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.CheckBoxPreference
import android.support.v7.preference.ListPreference import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor
@ -16,7 +17,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -35,28 +35,37 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
override val baseUrl = "https://lectortmo.com" override val baseUrl = "https://lectortmo.com"
val baseurl = HttpUrl.parse(baseUrl)!!
override val lang = "es" override val lang = "es"
override val supportsLatest = true override val supportsLatest = true
private val userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" private val imageCDNUrl = "https://img1.japanreader.com"
private val rateLimitInterceptor = SpecificHostRateLimitInterceptor(baseurl, 10, 60) // real ratelimit values private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
override val client: OkHttpClient = network.client.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)
.build()
override fun headersBuilder(): Headers.Builder {
return Headers.Builder()
.add("User-Agent", userAgent)
.add("Referer", "$baseUrl/")
.add("Cache-mode", "no-cache")
} }
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title&_page=1&page=$page", headers) private val webRateLimitInterceptor = SpecificHostRateLimitInterceptor(
HttpUrl.parse(baseUrl)!!,
preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
60
)
private val imageCDNRateLimitInterceptor = SpecificHostRateLimitInterceptor(
HttpUrl.parse(imageCDNUrl)!!,
preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
60
)
override val client: OkHttpClient = network.client.newBuilder()
.addNetworkInterceptor(webRateLimitInterceptor)
.addNetworkInterceptor(imageCDNRateLimitInterceptor)
.build()
// Marks erotic content as false and excludes: Ecchi(6), GirlsLove(17), BoysLove(18) and Harem(19) genders
private val getSFWUrlPart = if (getSFWModePref()) "&exclude_genders%5B%5D=6&exclude_genders%5B%5D=17&exclude_genders%5B%5D=18&exclude_genders%5B%5D=19&erotic=false" else ""
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title$getSFWUrlPart&_page=1&page=$page", headers)
override fun popularMangaNextPageSelector() = "a.page-link" override fun popularMangaNextPageSelector() = "a.page-link"
@ -70,7 +79,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
} }
} }
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title&_page=1&page=$page", headers) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title$getSFWUrlPart&_page=1&page=$page", headers)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
@ -80,11 +89,15 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/library")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/library")!!.newBuilder()
url.addQueryParameter("title", query) url.addQueryParameter("title", query)
if (getSFWModePref()) {
SFW_MODE_PREF_EXCLUDE_GENDERS.forEach { gender ->
url.addQueryParameter("exclude_genders[]", gender)
}
url.addQueryParameter("erotic", "false")
}
url.addQueryParameter("page", page.toString()) url.addQueryParameter("page", page.toString())
url.addQueryParameter("_page", "1") // Extra Query to Prevent Scrapping aka without it = 403 url.addQueryParameter("_page", "1") // Extra Query to Prevent Scrapping aka without it = 403
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is Types -> { is Types -> {
@ -93,6 +106,12 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
is Demography -> { is Demography -> {
url.addQueryParameter("demography", filter.toUriPart()) url.addQueryParameter("demography", filter.toUriPart())
} }
is Status -> {
url.addQueryParameter("status", filter.toUriPart())
}
is TranslationStatus -> {
url.addQueryParameter("translation_status", filter.toUriPart())
}
is FilterBy -> { is FilterBy -> {
url.addQueryParameter("filter_by", filter.toUriPart()) url.addQueryParameter("filter_by", filter.toUriPart())
} }
@ -105,93 +124,58 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
) )
} }
} }
is WebcomicFilter -> { is ContentTypeList -> {
url.addQueryParameter( filter.state.forEach { content ->
"webcomic", // If (SFW mode is not enabled) OR (SFW mode is enabled AND filter != erotic) -> Apply filter
when (filter.state) { // else -> ignore filter
Filter.TriState.STATE_INCLUDE -> "true" if (!getSFWModePref() || (getSFWModePref() && content.id != "erotic")) {
Filter.TriState.STATE_EXCLUDE -> "false" when (content.state) {
else -> "" Filter.TriState.STATE_IGNORE -> url.addQueryParameter(content.id, "")
Filter.TriState.STATE_INCLUDE -> url.addQueryParameter(content.id, "true")
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter(content.id, "false")
}
} }
) }
}
is FourKomaFilter -> {
url.addQueryParameter(
"yonkoma",
when (filter.state) {
Filter.TriState.STATE_INCLUDE -> "true"
Filter.TriState.STATE_EXCLUDE -> "false"
else -> ""
}
)
}
is AmateurFilter -> {
url.addQueryParameter(
"amateur",
when (filter.state) {
Filter.TriState.STATE_INCLUDE -> "true"
Filter.TriState.STATE_EXCLUDE -> "false"
else -> ""
}
)
}
is EroticFilter -> {
url.addQueryParameter(
"erotic",
when (filter.state) {
Filter.TriState.STATE_INCLUDE -> "true"
Filter.TriState.STATE_EXCLUDE -> "false"
else -> ""
}
)
} }
is GenreList -> { is GenreList -> {
filter.state filter.state.forEach { genre ->
.filter { genre -> genre.state } when (genre.state) {
.forEach { genre -> url.addQueryParameter("genders[]", genre.id) } Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("exclude_genders[]", genre.id)
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("genders[]", genre.id)
}
}
} }
} }
} }
return GET(url.build().toString(), headers) return GET(url.build().toString(), headers)
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun mangaDetailsParse(document: Document) = SManga.create().apply { override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h2.element-subtitle").text() title = document.select("h2.element-subtitle").text()
document.select("h5.card-title").let { document.select("h5.card-title").let {
author = it?.first()?.attr("title")?.substringAfter(", ") author = it?.first()?.attr("title")?.substringAfter(", ")
artist = it?.last()?.attr("title")?.substringAfter(", ") artist = it?.last()?.attr("title")?.substringAfter(", ")
} }
genre = document.select("a.py-2").joinToString(", ") { genre = document.select("a.py-2").joinToString(", ") {
it.text() it.text()
} }
description = document.select("p.element-description")?.text() description = document.select("p.element-description")?.text()
status = parseStatus(document.select("span.book-status")?.text().orEmpty()) status = parseStatus(document.select("span.book-status")?.text().orEmpty())
thumbnail_url = document.select(".book-thumbnail").attr("src") thumbnail_url = document.select(".book-thumbnail").attr("src")
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
status.contains("Publicándose") -> SManga.ONGOING status.contains("Publicándose") -> SManga.ONGOING
status.contains("Finalizado") -> SManga.COMPLETED status.contains("Finalizado") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()
// One-shot // One-shot
if (document.select("div.chapters").isEmpty()) { if (document.select("div.chapters").isEmpty()) {
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) } return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) }
} }
// Regular list of chapters // Regular list of chapters
val chapters = mutableListOf<SChapter>() val chapters = mutableListOf<SChapter>()
document.select(regularChapterListSelector()).forEach { chapelement -> document.select(regularChapterListSelector()).forEach { chapelement ->
@ -202,22 +186,17 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
.toFloat() .toFloat()
val chaptername = chapelement.select("div.col-10.text-truncate").text() val chaptername = chapelement.select("div.col-10.text-truncate").text()
val scanelement = chapelement.select("ul.chapter-list > li") val scanelement = chapelement.select("ul.chapter-list > li")
val dupselect = getduppref()!! if (getScanlatorPref()) {
if (dupselect == "one") {
scanelement.first { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) }
} else {
scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) } scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) }
} else {
scanelement.first { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) }
} }
} }
return chapters return chapters
} }
override fun chapterListSelector() = throw UnsupportedOperationException("Not used") override fun chapterListSelector() = throw UnsupportedOperationException("Not used")
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used") override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used")
private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item" private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item"
private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply { private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply {
url = element.select("div.row > .text-right > a").attr("href") url = element.select("div.row > .text-right > a").attr("href")
name = "One Shot" name = "One Shot"
@ -225,9 +204,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) }
?: 0 ?: 0
} }
private fun regularChapterListSelector() = "div.chapters > ul.list-group li.p-0.list-group-item" private fun regularChapterListSelector() = "div.chapters > ul.list-group li.p-0.list-group-item"
private fun regularChapterFromElement(element: Element, chName: String, number: Float) = SChapter.create().apply { private fun regularChapterFromElement(element: Element, chName: String, number: Float) = SChapter.create().apply {
url = element.select("div.row > .text-right > a").attr("href") url = element.select("div.row > .text-right > a").attr("href")
name = chName name = chName
@ -236,25 +213,20 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) }
?: 0 ?: 0
} }
private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time
?: 0 ?: 0
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri() val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri()
// Get /cascade instead of /paginate to get all pages at once // Get /cascade instead of /paginate to get all pages at once
val newUrl = if (getPageMethod() == "cascade" && currentUrl.contains("paginated")) { val newUrl = if (getPageMethodPref() == "cascade" && currentUrl.contains("paginated")) {
currentUrl.substringBefore("paginated") + "cascade" currentUrl.substringBefore("paginated") + "cascade"
} else if (getPageMethod() == "paginated" && currentUrl.contains("cascade")) { } else if (getPageMethodPref() == "paginated" && currentUrl.contains("cascade")) {
currentUrl.substringBefore("cascade") + "paginated" currentUrl.substringBefore("cascade") + "paginated"
} else currentUrl } else currentUrl
return GET(newUrl, headers) return GET(newUrl, headers)
} }
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
if (getPageMethod() == "cascade") { if (getPageMethodPref() == "cascade") {
document.select("div.viewer-container img").forEach { document.select("div.viewer-container img").forEach {
add( add(
Page( Page(
@ -275,8 +247,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
} }
} }
} }
// Note: At this moment (15/02/2021) it's necessary to make the image request without headers to prevent 403.
// Note: At this moment (13/07/2020) it's necessary to make the image request without headers to prevent 403.
override fun imageRequest(page: Page) = GET(page.imageUrl!!) override fun imageRequest(page: Page) = GET(page.imageUrl!!)
override fun imageUrlParse(document: Document): String { override fun imageUrlParse(document: Document): String {
@ -319,6 +290,27 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
) )
) )
private class Status : UriPartFilter(
"Filtrar por estado de serie",
arrayOf(
Pair("Ver todo", ""),
Pair("Publicándose", "publishing"),
Pair("Finalizado", "ended"),
Pair("Cancelado", "cancelled"),
Pair("Pausado", "on_hold")
)
)
private class TranslationStatus : UriPartFilter(
"Filtrar por estado de traducción",
arrayOf(
Pair("Ver todo", ""),
Pair("Activo", "publishing"),
Pair("Finalizado", "ended"),
Pair("Abandonado", "cancelled")
)
)
private class Demography : UriPartFilter( private class Demography : UriPartFilter(
"Filtrar por demografía", "Filtrar por demografía",
arrayOf( arrayOf(
@ -332,7 +324,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
) )
private class FilterBy : UriPartFilter( private class FilterBy : UriPartFilter(
"Campo de orden", "Filtrar por",
arrayOf( arrayOf(
Pair("Título", "title"), Pair("Título", "title"),
Pair("Autor", "author"), Pair("Autor", "author"),
@ -346,36 +338,38 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
Selection(0, false) Selection(0, false)
) )
private class WebcomicFilter : Filter.TriState("Webcomic") private class ContentType(name: String, val id: String) : Filter.TriState(name)
private class FourKomaFilter : Filter.TriState("Yonkoma") private class ContentTypeList(content: List<ContentType>) : Filter.Group<ContentType>("Filtrar por tipo de contenido", content)
private class AmateurFilter : Filter.TriState("Amateur") private class Genre(name: String, val id: String) : Filter.TriState(name)
private class EroticFilter : Filter.TriState("Erótico")
private class Genre(name: String, val id: String) : Filter.CheckBox(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Types(), Types(),
Filter.Separator(),
Filter.Header("Ignorado sino se filtra por tipo"),
Status(),
Filter.Separator(),
Filter.Header("Ignorado sino se filtra por tipo"),
TranslationStatus(),
Filter.Separator(),
Demography(), Demography(),
Filter.Separator(), Filter.Separator(),
FilterBy(), FilterBy(),
Filter.Separator(),
SortBy(), SortBy(),
Filter.Separator(), Filter.Separator(),
WebcomicFilter(), ContentTypeList(getContentTypeList()),
FourKomaFilter(), Filter.Separator(),
AmateurFilter(),
EroticFilter(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control')) // Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control'))
// .map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') // .map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n')
// on https://tumangaonline.me/library // on https://lectortmo.com/library
// Last revision 13/07/2020 // Last revision 15/02/2021
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Acción", "1"), Genre("Acción", "1"),
Genre("Aventura", "2"), Genre("Aventura", "2"),
@ -427,94 +421,238 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
Genre("Oeste", "48") Genre("Oeste", "48")
) )
private fun getContentTypeList() = listOf(
ContentType("Webcomic", "webcomic"),
ContentType("Yonkoma", "yonkoma"),
ContentType("Amateur", "amateur"),
ContentType("Erótico", "erotic")
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val deduppref = androidx.preference.ListPreference(screen.context).apply {
key = DEDUP_PREF_Title val SFWModePref = androidx.preference.CheckBoxPreference(screen.context).apply {
title = DEDUP_PREF_Title key = SFW_MODE_PREF
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator") title = SFW_MODE_PREF_TITLE
entryValues = arrayOf("all", "one") summary = SFW_MODE_PREF_SUMMARY
summary = "%s" setDefaultValue(SFW_MODE_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val checkValue = newValue as Boolean
val index = this.findIndexOfValue(selected) preferences.edit().putBoolean(SFW_MODE_PREF, checkValue).commit()
val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
val pageMethod = androidx.preference.ListPreference(screen.context).apply { val scanlatorPref = androidx.preference.CheckBoxPreference(screen.context).apply {
key = PAGEGET_PREF_Title key = SCANLATOR_PREF
title = PAGEGET_PREF_Title title = SCANLATOR_PREF_TITLE
entries = arrayOf("Cascada (recomendado)", "Paginado") summary = SCANLATOR_PREF_SUMMARY
setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
val checkValue = newValue as Boolean
preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit()
}
}
val pageMethodPref = androidx.preference.ListPreference(screen.context).apply {
key = PAGE_METHOD_PREF
title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada", "Páginado")
entryValues = arrayOf("cascade", "paginated") entryValues = arrayOf("cascade", "paginated")
summary = "%s" summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String try {
val index = this.findIndexOfValue(selected) val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
val entry = entryValues[index] as String setting
preferences.edit().putString(PAGEGET_PREF, entry).commit() } catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
screen.addPreference(deduppref) // Rate limit
screen.addPreference(pageMethod) val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply {
key = WEB_RATELIMIT_PREF
title = WEB_RATELIMIT_PREF_TITLE
summary = WEB_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply {
key = IMAGE_CDN_RATELIMIT_PREF
title = IMAGE_CDN_RATELIMIT_PREF_TITLE
summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(SFWModePref)
screen.addPreference(scanlatorPref)
screen.addPreference(pageMethodPref)
screen.addPreference(apiRateLimitPreference)
screen.addPreference(imgCDNRateLimitPreference)
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val deduppref = ListPreference(screen.context).apply {
key = DEDUP_PREF_Title val SFWModePref = CheckBoxPreference(screen.context).apply {
title = DEDUP_PREF_Title key = SFW_MODE_PREF
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator") title = SFW_MODE_PREF_TITLE
entryValues = arrayOf("all", "one") summary = SFW_MODE_PREF_SUMMARY
summary = "%s" setDefaultValue(SFW_MODE_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val checkValue = newValue as Boolean
val index = this.findIndexOfValue(selected) preferences.edit().putBoolean(SFW_MODE_PREF, checkValue).commit()
val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
val pageMethod = ListPreference(screen.context).apply { val scanlatorPref = CheckBoxPreference(screen.context).apply {
key = PAGEGET_PREF_Title key = SCANLATOR_PREF
title = PAGEGET_PREF_Title title = SCANLATOR_PREF_TITLE
entries = arrayOf("Cascada (recomendado)", "Paginado") summary = SCANLATOR_PREF_SUMMARY
setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
val checkValue = newValue as Boolean
preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit()
}
}
val pageMethodPref = ListPreference(screen.context).apply {
key = PAGE_METHOD_PREF
title = PAGE_METHOD_PREF_TITLE
entries = arrayOf("Cascada", "Páginado")
entryValues = arrayOf("cascade", "paginated") entryValues = arrayOf("cascade", "paginated")
summary = "%s" summary = PAGE_METHOD_PREF_SUMMARY
setDefaultValue(PAGE_METHOD_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String try {
val index = this.findIndexOfValue(selected) val setting = preferences.edit().putString(PAGE_METHOD_PREF, newValue as String).commit()
val entry = entryValues[index] as String setting
preferences.edit().putString(PAGEGET_PREF, entry).commit() } catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
screen.addPreference(deduppref) // Rate limit
screen.addPreference(pageMethod) val apiRateLimitPreference = ListPreference(screen.context).apply {
key = WEB_RATELIMIT_PREF
title = WEB_RATELIMIT_PREF_TITLE
summary = WEB_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
val imgCDNRateLimitPreference = ListPreference(screen.context).apply {
key = IMAGE_CDN_RATELIMIT_PREF
title = IMAGE_CDN_RATELIMIT_PREF_TITLE
summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY
entries = ENTRIES_ARRAY
entryValues = ENTRIES_ARRAY
setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
try {
val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
screen.addPreference(SFWModePref)
screen.addPreference(scanlatorPref)
screen.addPreference(pageMethodPref)
screen.addPreference(apiRateLimitPreference)
screen.addPreference(imgCDNRateLimitPreference)
} }
private fun getduppref() = preferences.getString(DEDUP_PREF, "all") private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE)
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade") private fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE)
private fun getPageMethodPref() = preferences.getString(PAGE_METHOD_PREF, PAGE_METHOD_PREF_DEFAULT_VALUE)
companion object { companion object {
private const val DEDUP_PREF_Title = "Preferencias de scanlator" private const val SCANLATOR_PREF = "scanlatorPref"
private const val DEDUP_PREF = "deduppref" private const val SCANLATOR_PREF_TITLE = "Mostrar todos los scanlator"
private const val PAGEGET_PREF_Title = "Método para la descarga de imágenes" private const val SCANLATOR_PREF_SUMMARY = "Se mostraran capítulos repetidos pero con diferentes Scanlators"
private const val PAGEGET_PREF = "pagemethodpref" private const val SCANLATOR_PREF_DEFAULT_VALUE = true
private const val SFW_MODE_PREF = "SFWModePref"
private const val SFW_MODE_PREF_TITLE = "Ocultar contenido NSFW"
private const val SFW_MODE_PREF_SUMMARY = "Ocultar el contenido erótico (puede que aún activandolo se sigan mostrando portadas o series NSFW). Ten en cuenta que al activarlo se ignoran filtros al explorar y buscar.\nLos filtros ignorados son: Filtrar por tipo de contenido (Erotico) y el Filtrar por generos: Ecchi, Boys Love, Girls Love y Harem."
private const val SFW_MODE_PREF_DEFAULT_VALUE = false
private val SFW_MODE_PREF_EXCLUDE_GENDERS = listOf("6", "17", "18", "19")
private const val PAGE_METHOD_PREF = "pageMethodPref"
private const val PAGE_METHOD_PREF_TITLE = "Método para descargar imágenes"
private const val PAGE_METHOD_PREF_SUMMARY = "Previene ser banneado por el servidor cuando se usa la configuración \"Cascada\" ya que esta reduce la cantidad de solicitudes.\nPuedes usar \"Páginado\" cuando las imágenes no carguen usando \"Cascada\".\nConfiguración actual: %s"
private const val PAGE_METHOD_PREF_DEFAULT_VALUE = "cascade"
private const val WEB_RATELIMIT_PREF = "webRatelimitPreference"
// Ratelimit permits per second for main website
private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web"
// This value affects network request amount to TMO url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s
private const val WEB_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red a la URL de TMO. Reducir este valor puede disminuir la posibilidad de obtener un error HTTP 429, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s"
private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "10"
private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference"
// Ratelimit permits per second for image CDN
private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes"
// This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. Tachiyomi restart required. \nCurrent value: %s
private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red para descargar imágenes. Reducir este valor puede disminuir errores al cargar imagenes, pero la velocidad de descarga será más lenta. Se requiere reiniciar Tachiyomi. \nValor actual: %s"
private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "10"
private val ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray()
const val PREFIX_LIBRARY = "library" const val PREFIX_LIBRARY = "library"
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"