LectorTmo: Remove LectorManga and fix crash on some devices (#7508)
* override fetch methods * remove lectorManga
This commit is contained in:
parent
7dd76fe0c9
commit
2147b87816
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'TuMangaOnline / LectorManga'
|
||||
extClass = '.LectorTmoFactory'
|
||||
extVersionCode = 4
|
||||
extName = 'TuMangaOnline'
|
||||
extClass = '.LectorTmo'
|
||||
extVersionCode = 6
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ import android.annotation.SuppressLint
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
@ -34,16 +32,20 @@ import java.security.SecureRandom
|
|||
import java.security.cert.X509Certificate
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
abstract class LectorTmo(
|
||||
override val name: String,
|
||||
override val baseUrl: String,
|
||||
override val lang: String,
|
||||
private val rateLimitClient: OkHttpClient,
|
||||
) : ParsedHttpSource(), ConfigurableSource {
|
||||
class LectorTmo : ParsedHttpSource(), ConfigurableSource {
|
||||
|
||||
override val id = 4146344224513899730
|
||||
|
||||
override val name = "TuMangaOnline"
|
||||
|
||||
override val baseUrl = "https://zonatmo.com"
|
||||
|
||||
override val lang = "es"
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
@ -56,19 +58,6 @@ abstract class LectorTmo(
|
|||
.set("Referer", "$baseUrl/")
|
||||
.build()
|
||||
|
||||
protected open val imageCDNUrls = arrayOf(
|
||||
"https://img1.japanreader.com",
|
||||
"https://japanreader.com",
|
||||
"https://imgtmo.com",
|
||||
)
|
||||
|
||||
private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array<String>, permits: Int, period: Long): OkHttpClient.Builder {
|
||||
hosts.forEach { host ->
|
||||
rateLimitHost(host.toHttpUrl(), permits, period)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder {
|
||||
val naiveTrustManager = @SuppressLint("CustomX509TrustManager")
|
||||
object : X509TrustManager {
|
||||
|
@ -77,7 +66,7 @@ abstract class LectorTmo(
|
|||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||
}
|
||||
|
||||
val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
|
||||
val insecureSocketFactory = SSLContext.getInstance("SSL").apply {
|
||||
val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
|
||||
init(null, trustAllCerts, SecureRandom())
|
||||
}.socketFactory
|
||||
|
@ -87,28 +76,18 @@ abstract class LectorTmo(
|
|||
return this
|
||||
}
|
||||
|
||||
private val ignoreSslClient: OkHttpClient by lazy {
|
||||
rateLimitClient.newBuilder()
|
||||
override val client: OkHttpClient by lazy {
|
||||
network.cloudflareClient.newBuilder()
|
||||
.ignoreAllSSLErrors()
|
||||
.followRedirects(false)
|
||||
.rateLimit(
|
||||
preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
|
||||
60,
|
||||
)
|
||||
.rateLimit(3, 1, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
private var lastCFDomain: String = ""
|
||||
override val client: OkHttpClient by lazy {
|
||||
rateLimitClient.newBuilder()
|
||||
.addInterceptor { chain ->
|
||||
val request = chain.request()
|
||||
val url = request.url
|
||||
if (url.fragment == "imagereq") {
|
||||
return@addInterceptor ignoreSslClient.newCall(request).execute()
|
||||
}
|
||||
chain.proceed(request)
|
||||
}
|
||||
|
||||
// Used on all request except on image requests
|
||||
private val safeClient: OkHttpClient by lazy {
|
||||
network.cloudflareClient.newBuilder()
|
||||
.addInterceptor { chain ->
|
||||
if (!getSaveLastCFUrlPref()) return@addInterceptor chain.proceed(chain.request())
|
||||
val request = chain.request()
|
||||
|
@ -118,22 +97,21 @@ abstract class LectorTmo(
|
|||
}
|
||||
response
|
||||
}
|
||||
.rateLimitHost(
|
||||
baseUrl.toHttpUrl(),
|
||||
preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
|
||||
60,
|
||||
)
|
||||
.rateLimitImageCDNs(
|
||||
imageCDNUrls,
|
||||
preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(),
|
||||
60,
|
||||
)
|
||||
.rateLimit(1, 3, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Marks erotic content as false and excludes: Ecchi(6), GirlsLove(17), BoysLove(18), Harem(19), Trap(94) genders
|
||||
private fun getSFWUrlPart(): String = if (getSFWModePref()) "&exclude_genders%5B%5D=6&exclude_genders%5B%5D=17&exclude_genders%5B%5D=18&exclude_genders%5B%5D=19&exclude_genders%5B%5D=94&erotic=false" else ""
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return safeClient.newCall(popularMangaRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
popularMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title${getSFWUrlPart()}&_pg=1&page=$page", tmoHeaders)
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a[rel='next']"
|
||||
|
@ -148,6 +126,14 @@ abstract class LectorTmo(
|
|||
}
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return safeClient.newCall(latestUpdatesRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
latestUpdatesParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title${getSFWUrlPart()}&_pg=1&page=$page", tmoHeaders)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
@ -160,7 +146,7 @@ abstract class LectorTmo(
|
|||
return if (query.startsWith(PREFIX_SLUG_SEARCH)) {
|
||||
val realQuery = query.removePrefix(PREFIX_SLUG_SEARCH)
|
||||
|
||||
client.newCall(searchMangaBySlugRequest(realQuery))
|
||||
safeClient.newCall(searchMangaBySlugRequest(realQuery))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
val details = mangaDetailsParse(response)
|
||||
|
@ -168,7 +154,7 @@ abstract class LectorTmo(
|
|||
MangasPage(listOf(details), false)
|
||||
}
|
||||
} else {
|
||||
client.newCall(searchMangaRequest(page, query, filters))
|
||||
safeClient.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
searchMangaParse(response)
|
||||
|
@ -241,6 +227,14 @@ abstract class LectorTmo(
|
|||
return super.getMangaUrl(manga)
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return safeClient.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
mangaDetailsParse(response).apply { initialized = true }
|
||||
}
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga) = GET(baseUrl + manga.url, tmoHeaders)
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
|
@ -257,19 +251,27 @@ abstract class LectorTmo(
|
|||
thumbnail_url = document.select(".book-thumbnail").attr("src")
|
||||
}
|
||||
|
||||
protected fun parseStatus(status: String) = when {
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("Publicándose") -> SManga.ONGOING
|
||||
status.contains("Finalizado") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
protected open val oneShotChapterName = "One Shot"
|
||||
private val oneShotChapterName = "One Shot"
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
if (lastCFDomain.isNotEmpty()) return lastCFDomain.also { lastCFDomain = "" }
|
||||
return super.getChapterUrl(chapter)
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return safeClient.newCall(chapterListRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
chapterListParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
|
@ -294,15 +296,15 @@ abstract class LectorTmo(
|
|||
return chapters
|
||||
}
|
||||
|
||||
protected open val oneShotChapterListSelector = "div.chapter-list-element > ul.list-group li.list-group-item"
|
||||
private val oneShotChapterListSelector = "div.chapter-list-element > ul.list-group li.list-group-item"
|
||||
|
||||
protected open val regularChapterListSelector = "div.chapters > ul.list-group li.p-0.list-group-item"
|
||||
private val regularChapterListSelector = "div.chapters > ul.list-group li.p-0.list-group-item"
|
||||
|
||||
override fun chapterListSelector() = throw UnsupportedOperationException()
|
||||
|
||||
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException()
|
||||
|
||||
protected open fun chapterFromElement(element: Element, chName: String) = SChapter.create().apply {
|
||||
private fun chapterFromElement(element: Element, chName: String) = SChapter.create().apply {
|
||||
url = element.select("div.row > .text-right > a").attr("href")
|
||||
name = chName
|
||||
scanlator = element.select("div.col-md-6.text-truncate").text()
|
||||
|
@ -311,11 +313,19 @@ abstract class LectorTmo(
|
|||
} ?: 0
|
||||
}
|
||||
|
||||
protected open fun parseChapterDate(date: String): Long {
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
.parse(date)?.time ?: 0
|
||||
}
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
return safeClient.newCall(pageListRequest(chapter))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
pageListParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
return GET(chapter.url, tmoHeaders)
|
||||
}
|
||||
|
@ -440,7 +450,7 @@ abstract class LectorTmo(
|
|||
}
|
||||
|
||||
override fun imageRequest(page: Page) = GET(
|
||||
url = page.imageUrl!! + "#imagereq",
|
||||
url = page.imageUrl!!,
|
||||
headers = headers.newBuilder()
|
||||
.set("Referer", page.url.substringBefore("news/"))
|
||||
.build(),
|
||||
|
@ -564,11 +574,11 @@ abstract class LectorTmo(
|
|||
Genre("Trap", "94"),
|
||||
)
|
||||
|
||||
protected fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE)
|
||||
private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE)
|
||||
|
||||
protected fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE)
|
||||
private fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE)
|
||||
|
||||
protected fun getSaveLastCFUrlPref(): Boolean = preferences.getBoolean(SAVE_LAST_CF_URL_PREF, SAVE_LAST_CF_URL_PREF_DEFAULT_VALUE)
|
||||
private fun getSaveLastCFUrlPref(): Boolean = preferences.getBoolean(SAVE_LAST_CF_URL_PREF, SAVE_LAST_CF_URL_PREF_DEFAULT_VALUE)
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val sfwModePref = CheckBoxPreference(screen.context).apply {
|
||||
|
@ -585,25 +595,6 @@ abstract class LectorTmo(
|
|||
setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE)
|
||||
}
|
||||
|
||||
// Rate limit
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val saveLastCFUrlPreference = CheckBoxPreference(screen.context).apply {
|
||||
key = SAVE_LAST_CF_URL_PREF
|
||||
title = SAVE_LAST_CF_URL_PREF_TITLE
|
||||
|
@ -613,8 +604,6 @@ abstract class LectorTmo(
|
|||
|
||||
screen.addPreference(sfwModePref)
|
||||
screen.addPreference(scanlatorPref)
|
||||
screen.addPreference(apiRateLimitPreference)
|
||||
screen.addPreference(imgCDNRateLimitPreference)
|
||||
screen.addPreference(saveLastCFUrlPreference)
|
||||
}
|
||||
|
||||
|
@ -633,23 +622,11 @@ abstract class LectorTmo(
|
|||
private const val SFW_MODE_PREF_DEFAULT_VALUE = false
|
||||
private val SFW_MODE_PREF_EXCLUDE_GENDERS = listOf("6", "17", "18", "19")
|
||||
|
||||
private const val WEB_RATELIMIT_PREF = "webRatelimitPreference"
|
||||
private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web"
|
||||
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 la app. \nValor actual: %s"
|
||||
private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "8"
|
||||
|
||||
private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference"
|
||||
private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes"
|
||||
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 la app. \nValor actual: %s"
|
||||
private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "50"
|
||||
|
||||
private const val SAVE_LAST_CF_URL_PREF = "saveLastCFUrlPreference"
|
||||
private const val SAVE_LAST_CF_URL_PREF_TITLE = "Guardar la última URL con error de Cloudflare"
|
||||
private const val SAVE_LAST_CF_URL_PREF_SUMMARY = "Guarda la última URL con error de Cloudflare para que se pueda acceder a ella al abrir la serie en WebView."
|
||||
private const val SAVE_LAST_CF_URL_PREF_DEFAULT_VALUE = true
|
||||
|
||||
private val ENTRIES_ARRAY = listOf(1, 2, 3, 5, 6, 7, 8, 9, 10, 15, 20, 30, 40, 50, 100).map { i -> i.toString() }.toTypedArray()
|
||||
|
||||
const val PREFIX_LIBRARY = "library"
|
||||
const val PREFIX_SLUG_SEARCH = "slug:"
|
||||
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.es.lectortmo
|
||||
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LectorTmoFactory : SourceFactory {
|
||||
|
||||
override fun createSources() = listOf(
|
||||
LectorManga(),
|
||||
TuMangaOnline(),
|
||||
)
|
||||
}
|
||||
|
||||
val rateLimitClient = Injekt.get<NetworkHelper>().cloudflareClient.newBuilder()
|
||||
.rateLimit(1, 1500, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
class TuMangaOnline : LectorTmo("TuMangaOnline", "https://zonatmo.com", "es", rateLimitClient) {
|
||||
override val id = 4146344224513899730
|
||||
}
|
||||
|
||||
class LectorManga : LectorTmo("LectorManga", "https://lectormanga.com", "es", rateLimitClient) {
|
||||
override val id = 7925520943983324764
|
||||
|
||||
override fun popularMangaSelector() = ".col-6 .card"
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
title = element.select("a").text()
|
||||
thumbnail_url = element.select("img").attr("src")
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
document.selectFirst("h1:has(small)")?.let { title = it.ownText() }
|
||||
genre = document.select("a.py-2").joinToString(", ") {
|
||||
it.text()
|
||||
}
|
||||
description = document.select(".col-12.mt-2").text()
|
||||
status = parseStatus(document.select(".status-publishing").text())
|
||||
thumbnail_url = document.select(".text-center img.img-fluid").attr("src")
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply {
|
||||
val document = response.asJsoup()
|
||||
|
||||
// One-shot
|
||||
if (document.select("#chapters").isEmpty()) {
|
||||
return document.select(oneShotChapterListSelector).map { chapterFromElement(it, oneShotChapterName) }
|
||||
}
|
||||
|
||||
// Regular list of chapters
|
||||
val chapterNames = document.select("#chapters h4.text-truncate")
|
||||
val chapterInfos = document.select("#chapters .chapter-list")
|
||||
|
||||
chapterNames.forEachIndexed { index, _ ->
|
||||
val scanlator = chapterInfos[index].select("li")
|
||||
if (getScanlatorPref()) {
|
||||
scanlator.forEach { add(chapterFromElement(it, chapterNames[index].text())) }
|
||||
} else {
|
||||
scanlator.last { add(chapterFromElement(it, chapterNames[index].text())) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element, chName: String) = SChapter.create().apply {
|
||||
url = element.select("div.row > .text-right > a").attr("href")
|
||||
name = chName
|
||||
scanlator = element.select("div.col-12.text-truncate span").text()
|
||||
date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let {
|
||||
parseChapterDate(it)
|
||||
} ?: 0
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue