Fix TuMangaOnline & LetorManga (#3779)
* Fix inorichi/tachiyomi-extensions#3715 * Fix inorichi/tachiyomi-extensions#3715 for main source TMO * Update TMO gradle version
This commit is contained in:
parent
4f9a63317e
commit
8ef7624233
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'LectorManga'
|
extName = 'LectorManga'
|
||||||
pkgNameSuffix = 'es.lectormanga'
|
pkgNameSuffix = 'es.lectormanga'
|
||||||
extClass = '.LectorManga'
|
extClass = '.LectorManga'
|
||||||
extVersionCode = 11
|
extVersionCode = 12
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,7 @@ 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 java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -32,53 +29,41 @@ import uy.kohesive.injekt.api.get
|
|||||||
*/
|
*/
|
||||||
class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
// Info
|
|
||||||
|
|
||||||
override val name = "LectorManga"
|
override val name = "LectorManga"
|
||||||
|
|
||||||
override val baseUrl = "https://lectormanga.com"
|
override val baseUrl = "https://lectormanga.com"
|
||||||
|
|
||||||
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 userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
override fun headersBuilder(): Headers.Builder {
|
||||||
return Headers.Builder()
|
return Headers.Builder()
|
||||||
.add("User-Agent", userAgent)
|
.add("User-Agent", userAgent)
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBuilder(url: String, headers: Headers, formBody: FormBody?, method: String): String {
|
|
||||||
val req = Request.Builder()
|
|
||||||
.headers(headers)
|
|
||||||
.url(url)
|
|
||||||
.method(method, formBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return client.newCall(req)
|
|
||||||
.execute()
|
|
||||||
.request()
|
|
||||||
.url()
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular
|
|
||||||
|
|
||||||
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']"
|
||||||
|
|
||||||
override fun popularMangaSelector() = ".col-6 .card"
|
override fun popularMangaSelector() = ".col-6 .card"
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||||
title = element.select("a").text()
|
title = element.select("a").text()
|
||||||
thumbnail_url = element.select("img").attr("src")
|
thumbnail_url = element.select("img").attr("src")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&page=$page", headers)
|
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&page=$page", headers)
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
// Search
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||||
|
|
||||||
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()
|
||||||
@ -146,10 +131,10 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
// Details
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
genre = document.select("a.py-2").joinToString(", ") {
|
genre = document.select("a.py-2").joinToString(", ") {
|
||||||
@ -166,17 +151,12 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply {
|
override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply {
|
||||||
time = serverTime() // Get time when the chapter page is opened
|
|
||||||
|
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
val chapterUrl = response.request().url().toString()
|
|
||||||
|
|
||||||
// One-shot
|
// One-shot
|
||||||
if (document.select("#chapters").isEmpty()) {
|
if (document.select("#chapters").isEmpty()) {
|
||||||
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it, chapterUrl) }
|
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular list of chapters
|
// Regular list of chapters
|
||||||
@ -188,28 +168,29 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
chapterNames.forEachIndexed { index, _ ->
|
chapterNames.forEachIndexed { index, _ ->
|
||||||
val scanlator = chapterInfos[index].select("li")
|
val scanlator = chapterInfos[index].select("li")
|
||||||
if (dupselect == "one") {
|
if (dupselect == "one") {
|
||||||
scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
|
scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) }
|
||||||
} else {
|
} else {
|
||||||
scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
|
scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index])) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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, chapterUrl: String) = SChapter.create().apply {
|
private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
url = "$chapterUrl#${element.select("div.row > .text-right > form").attr("id")}"
|
url = element.select("div.row > .text-right > a").attr("href")
|
||||||
name = "One Shot"
|
name = "One Shot"
|
||||||
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
||||||
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 regularChapterFromElement(chapterName: String, info: Element, number: Float, chapterUrl: String) = SChapter.create().apply {
|
private fun regularChapterFromElement(chapterName: String, info: Element, number: Float) = SChapter.create().apply {
|
||||||
url = "$chapterUrl#${info.select("div.row > .text-right > form").attr("id")}"
|
url = info.select("div.row > .text-right > a").attr("href")
|
||||||
name = chapterName
|
name = chapterName
|
||||||
scanlator = info.select("div.col-md-6.text-truncate")?.text()
|
scanlator = info.select("div.col-md-6.text-truncate")?.text()
|
||||||
date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let {
|
date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let {
|
||||||
@ -223,56 +204,17 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
.parse(date)?.time ?: 0
|
.parse(date)?.time ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilities
|
|
||||||
|
|
||||||
private var time = serverTime() // Grab time at app launch, can be updated
|
|
||||||
private fun serverTime(): String {
|
|
||||||
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
|
||||||
formatter.timeZone = TimeZone.getTimeZone("GMT+1") // Convert time to match server
|
|
||||||
return formatter.format(Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pages
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val (chapterURL, chapterID) = chapter.url.split("#")
|
val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri()
|
||||||
val response = client.newCall(GET(chapterURL, headers)).execute() // Get chapter page for current token
|
|
||||||
if (!response.isSuccessful) throw Exception("Lector Manga HTTP Error ${response.code()}")
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val getUrl = document.select("form#$chapterID").attr("action") + "/$time" // Get redirect URL
|
|
||||||
val token = document.select("form#$chapterID input").attr("value") // Get token
|
|
||||||
val method = document.select("form#$chapterID").attr("method") // Check POST or GET
|
|
||||||
time = serverTime() // Update time for next chapter
|
|
||||||
|
|
||||||
val getHeaders = headersBuilder()
|
|
||||||
.add("User-Agent", userAgent)
|
|
||||||
.add("Referer", chapterURL)
|
|
||||||
.add("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val formBody = when (method) {
|
|
||||||
"GET" -> null
|
|
||||||
"POST" -> FormBody.Builder()
|
|
||||||
.add("_token", token)
|
|
||||||
.build()
|
|
||||||
else -> throw UnsupportedOperationException("Lector Manga something else broke.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val newUrl = getBuilder(getUrl, getHeaders, formBody, method)
|
|
||||||
|
|
||||||
// Get /cascade instead of /paginate to get all pages at once
|
// Get /cascade instead of /paginate to get all pages at once
|
||||||
val url = if (getPageMethod() == "cascade" && newUrl.contains("paginated")) {
|
val newUrl = if (getPageMethod() == "cascade" && currentUrl.contains("paginated")) {
|
||||||
newUrl.substringBefore("paginated") + "cascade"
|
currentUrl.substringBefore("paginated") + "cascade"
|
||||||
} else if (getPageMethod() == "paginated" && newUrl.contains("cascade")) {
|
} else if (getPageMethod() == "paginated" && currentUrl.contains("cascade")) {
|
||||||
newUrl.substringBefore("cascade") + "paginated"
|
currentUrl.substringBefore("cascade") + "paginated"
|
||||||
} else newUrl
|
} else currentUrl
|
||||||
|
|
||||||
val headers = headersBuilder()
|
return GET(newUrl, headers)
|
||||||
.add("User-Agent", userAgent)
|
|
||||||
.add("Referer", newUrl)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||||
@ -292,12 +234,13 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 imageUrlParse(document: Document): String = document.select("img.viewer-image").attr("src")
|
override fun imageUrlParse(document: Document): String = document.select("img.viewer-image").attr("src")
|
||||||
|
|
||||||
// Filters
|
|
||||||
|
|
||||||
private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
|
private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
|
||||||
Pair("Ver todo", ""),
|
Pair("Ver todos", ""),
|
||||||
Pair("Manga", "manga"),
|
Pair("Manga", "manga"),
|
||||||
Pair("Manhua", "manhua"),
|
Pair("Manhua", "manhua"),
|
||||||
Pair("Manhwa", "manhwa"),
|
Pair("Manhwa", "manhwa"),
|
||||||
@ -308,7 +251,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
))
|
))
|
||||||
|
|
||||||
private class Demography : UriPartFilter("Filtrar por demografía", arrayOf(
|
private class Demography : UriPartFilter("Filtrar por demografía", arrayOf(
|
||||||
Pair("Ver todo", ""),
|
Pair("Ver todas", ""),
|
||||||
Pair("Seinen", "seinen"),
|
Pair("Seinen", "seinen"),
|
||||||
Pair("Shoujo", "shoujo"),
|
Pair("Shoujo", "shoujo"),
|
||||||
Pair("Shounen", "shounen"),
|
Pair("Shounen", "shounen"),
|
||||||
@ -329,11 +272,15 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private class WebcomicFilter : Filter.TriState("Webcomic")
|
private class WebcomicFilter : Filter.TriState("Webcomic")
|
||||||
|
|
||||||
private class FourKomaFilter : Filter.TriState("Yonkoma")
|
private class FourKomaFilter : Filter.TriState("Yonkoma")
|
||||||
|
|
||||||
private class AmateurFilter : Filter.TriState("Amateur")
|
private class AmateurFilter : Filter.TriState("Amateur")
|
||||||
|
|
||||||
private class EroticFilter : Filter.TriState("Erótico")
|
private class EroticFilter : Filter.TriState("Erótico")
|
||||||
|
|
||||||
private class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
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(
|
||||||
@ -353,7 +300,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
// Array.from(document.querySelectorAll('#advancedSearch .custom-checkbox'))
|
// Array.from(document.querySelectorAll('#advancedSearch .custom-checkbox'))
|
||||||
// .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://lectormanga.com/library
|
// on https://lectormanga.com/library
|
||||||
// Last revision 13/04/2020
|
// Last revision 13/07/2020
|
||||||
private fun getGenreList() = listOf(
|
private fun getGenreList() = listOf(
|
||||||
Genre("Acción", "1"),
|
Genre("Acción", "1"),
|
||||||
Genre("Aventura", "2"),
|
Genre("Aventura", "2"),
|
||||||
@ -420,7 +367,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val deduppref = androidx.preference.ListPreference(screen.context).apply {
|
val deduppref = androidx.preference.ListPreference(screen.context).apply {
|
||||||
key = DEDUP_PREF_Title
|
key = DEDUP_PREF_Title
|
||||||
title = DEDUP_PREF_Title
|
title = DEDUP_PREF_Title
|
||||||
entries = arrayOf("All scanlators", "One scanlator per chapter")
|
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator")
|
||||||
entryValues = arrayOf("all", "one")
|
entryValues = arrayOf("all", "one")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -435,7 +382,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val pageMethod = androidx.preference.ListPreference(screen.context).apply {
|
val pageMethod = androidx.preference.ListPreference(screen.context).apply {
|
||||||
key = PAGEGET_PREF_Title
|
key = PAGEGET_PREF_Title
|
||||||
title = PAGEGET_PREF_Title
|
title = PAGEGET_PREF_Title
|
||||||
entries = arrayOf("Cascada", "Paginada")
|
entries = arrayOf("Cascada (recomendado)", "Paginado")
|
||||||
entryValues = arrayOf("cascade", "paginated")
|
entryValues = arrayOf("cascade", "paginated")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -455,7 +402,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val deduppref = ListPreference(screen.context).apply {
|
val deduppref = ListPreference(screen.context).apply {
|
||||||
key = DEDUP_PREF_Title
|
key = DEDUP_PREF_Title
|
||||||
title = DEDUP_PREF_Title
|
title = DEDUP_PREF_Title
|
||||||
entries = arrayOf("All scanlators", "One scanlator per chapter")
|
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator")
|
||||||
entryValues = arrayOf("all", "one")
|
entryValues = arrayOf("all", "one")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -470,7 +417,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val pageMethod = ListPreference(screen.context).apply {
|
val pageMethod = ListPreference(screen.context).apply {
|
||||||
key = PAGEGET_PREF_Title
|
key = PAGEGET_PREF_Title
|
||||||
title = PAGEGET_PREF_Title
|
title = PAGEGET_PREF_Title
|
||||||
entries = arrayOf("Cascada", "Paginada")
|
entries = arrayOf("Cascada (recomendado)", "Paginado")
|
||||||
entryValues = arrayOf("cascade", "paginated")
|
entryValues = arrayOf("cascade", "paginated")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -487,12 +434,13 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getduppref() = preferences.getString(DEDUP_PREF, "all")
|
private fun getduppref() = preferences.getString(DEDUP_PREF, "all")
|
||||||
|
|
||||||
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade")
|
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DEDUP_PREF_Title = "Chapter List Scanlator Preference"
|
private const val DEDUP_PREF_Title = "Preferencias de scanlator"
|
||||||
private const val DEDUP_PREF = "deduppref"
|
private const val DEDUP_PREF = "deduppref"
|
||||||
private const val PAGEGET_PREF_Title = "Método para obtener imágenes"
|
private const val PAGEGET_PREF_Title = "Método para la descarga de imágenes"
|
||||||
private const val PAGEGET_PREF = "pagemethodpref"
|
private const val PAGEGET_PREF = "pagemethodpref"
|
||||||
|
|
||||||
private val SORTABLES = listOf(
|
private val SORTABLES = listOf(
|
||||||
|
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'TuMangaOnline'
|
extName = 'TuMangaOnline'
|
||||||
pkgNameSuffix = 'es.tumangaonline'
|
pkgNameSuffix = 'es.tumangaonline'
|
||||||
extClass = '.TuMangaOnline'
|
extClass = '.TuMangaOnline'
|
||||||
extVersionCode = 26
|
extVersionCode = 27
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
|||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -27,16 +26,16 @@ import uy.kohesive.injekt.api.get
|
|||||||
|
|
||||||
class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
// Info
|
|
||||||
|
|
||||||
override val name = "TuMangaOnline"
|
override val name = "TuMangaOnline"
|
||||||
|
|
||||||
override val baseUrl = "https://lectortmo.com"
|
override val baseUrl = "https://lectortmo.com"
|
||||||
|
|
||||||
override val lang = "es"
|
override val lang = "es"
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
// Client
|
|
||||||
|
|
||||||
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 userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
override fun headersBuilder(): Headers.Builder {
|
||||||
return Headers.Builder()
|
return Headers.Builder()
|
||||||
.add("User-Agent", userAgent)
|
.add("User-Agent", userAgent)
|
||||||
@ -44,25 +43,12 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
.add("Cache-mode", "no-cache")
|
.add("Cache-mode", "no-cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBuilder(url: String, headers: Headers, formBody: FormBody?, method: String): String {
|
|
||||||
val req = Request.Builder()
|
|
||||||
.headers(headers)
|
|
||||||
.url(url)
|
|
||||||
.method(method, formBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return client.newCall(req)
|
|
||||||
.execute()
|
|
||||||
.request()
|
|
||||||
.url()
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title&_page=1&page=$page", headers)
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title&_page=1&page=$page", headers)
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = "a.page-link"
|
override fun popularMangaNextPageSelector() = "a.page-link"
|
||||||
|
|
||||||
override fun popularMangaSelector() = "div.element"
|
override fun popularMangaSelector() = "div.element"
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
element.select("div.element > a").let {
|
element.select("div.element > a").let {
|
||||||
setUrlWithoutDomain(it.attr("href").substringAfter(" "))
|
setUrlWithoutDomain(it.attr("href").substringAfter(" "))
|
||||||
@ -71,14 +57,13 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Latest
|
|
||||||
|
|
||||||
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&_page=1&page=$page", headers)
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
// Search
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
|
||||||
|
|
||||||
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()
|
||||||
@ -147,10 +132,10 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
// Details
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
document.select("h5.card-title").let {
|
document.select("h5.card-title").let {
|
||||||
@ -173,15 +158,12 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
val chapterUrl = response.request().url().toString()
|
|
||||||
|
|
||||||
// One-shot
|
// One-shot
|
||||||
if (document.select("div.chapters").isEmpty()) {
|
if (document.select("div.chapters").isEmpty()) {
|
||||||
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it, chapterUrl) }
|
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular list of chapters
|
// Regular list of chapters
|
||||||
@ -196,9 +178,9 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val scanelement = chapelement.select("ul.chapter-list > li")
|
val scanelement = chapelement.select("ul.chapter-list > li")
|
||||||
val dupselect = getduppref()!!
|
val dupselect = getduppref()!!
|
||||||
if (dupselect == "one") {
|
if (dupselect == "one") {
|
||||||
scanelement.first { chapters.add(regularChapterFromElement(it, chaptername, chapternumber, chapterUrl)) }
|
scanelement.first { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) }
|
||||||
} else {
|
} else {
|
||||||
scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, chapternumber, chapterUrl)) }
|
scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, chapternumber)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chapters
|
return chapters
|
||||||
@ -210,8 +192,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
|
|
||||||
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, chapterUrl: String) = SChapter.create().apply {
|
private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
url = "$chapterUrl#${element.select("div.row > .text-right > form").attr("id")}"
|
url = element.select("div.row > .text-right > a").attr("href")
|
||||||
name = "One Shot"
|
name = "One Shot"
|
||||||
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
||||||
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) }
|
||||||
@ -220,8 +202,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
|
|
||||||
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, chapterUrl: String) = SChapter.create().apply {
|
private fun regularChapterFromElement(element: Element, chName: String, number: Float) = SChapter.create().apply {
|
||||||
url = "$chapterUrl#${element.select("div.row > .text-right > form").attr("id")}"
|
url = element.select("div.row > .text-right > a").attr("href")
|
||||||
name = chName
|
name = chName
|
||||||
chapter_number = number
|
chapter_number = number
|
||||||
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
scanlator = element.select("div.col-md-6.text-truncate")?.text()
|
||||||
@ -232,42 +214,17 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
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
|
||||||
|
|
||||||
// Pages
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val (chapterURL, chapterID) = chapter.url.split("#")
|
val currentUrl = client.newCall(GET(chapter.url, headers)).execute().asJsoup().body().baseUri()
|
||||||
val response = client.newCall(GET(chapterURL, headers)).execute() // Get chapter page for current token
|
|
||||||
if (!response.isSuccessful) throw Exception("TMO HTTP Error ${response.code()}")
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val script = document.select("script:containsData(submitChapterForm)").html()
|
|
||||||
val tmotk = script.substringAfter("action+\"").substringBefore("\"}")
|
|
||||||
val geturl = document.select("form#$chapterID").attr("action") + tmotk // Get redirect URL
|
|
||||||
val token = document.select("form#$chapterID input").attr("value") // Get token
|
|
||||||
val method = document.select("form#$chapterID").attr("method") // Check POST or GET
|
|
||||||
|
|
||||||
val getHeaders = headersBuilder()
|
// Get /cascade instead of /paginate to get all pages at once
|
||||||
.add("User-Agent", userAgent)
|
val newUrl = if (getPageMethod() == "cascade" && currentUrl.contains("paginated")) {
|
||||||
.add("Referer", chapterURL)
|
currentUrl.substringBefore("paginated") + "cascade"
|
||||||
.add("Content-Type", "application/x-www-form-urlencoded")
|
} else if (getPageMethod() == "paginated" && currentUrl.contains("cascade")) {
|
||||||
.build()
|
currentUrl.substringBefore("cascade") + "paginated"
|
||||||
|
} else currentUrl
|
||||||
|
|
||||||
val formBody = when (method) {
|
return GET(newUrl, headers)
|
||||||
"GET" -> null
|
|
||||||
"POST" -> FormBody.Builder()
|
|
||||||
.add("_token", token)
|
|
||||||
.build()
|
|
||||||
else -> throw UnsupportedOperationException("TMO Unknown method. Open GitHub issue")
|
|
||||||
}
|
|
||||||
|
|
||||||
val url = getBuilder(geturl, getHeaders, formBody, method).substringBeforeLast("/") + "/${getPageMethod()}"
|
|
||||||
// Get /cascade instead of /paginated to get all pages at once
|
|
||||||
|
|
||||||
val headers = headersBuilder()
|
|
||||||
.add("User-Agent", userAgent)
|
|
||||||
.add("Referer", "$baseUrl/library/manga/")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||||
@ -287,12 +244,13 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 imageUrlParse(document: Document): String {
|
override fun imageUrlParse(document: Document): String {
|
||||||
return document.select("div.viewer-container > div.img-container > img.viewer-image").attr("src")
|
return document.select("div.viewer-container > div.img-container > img.viewer-image").attr("src")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters
|
|
||||||
|
|
||||||
private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
|
private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
|
||||||
Pair("Ver todo", ""),
|
Pair("Ver todo", ""),
|
||||||
Pair("Manga", "manga"),
|
Pair("Manga", "manga"),
|
||||||
@ -326,11 +284,15 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private class WebcomicFilter : Filter.TriState("Webcomic")
|
private class WebcomicFilter : Filter.TriState("Webcomic")
|
||||||
|
|
||||||
private class FourKomaFilter : Filter.TriState("Yonkoma")
|
private class FourKomaFilter : Filter.TriState("Yonkoma")
|
||||||
|
|
||||||
private class AmateurFilter : Filter.TriState("Amateur")
|
private class AmateurFilter : Filter.TriState("Amateur")
|
||||||
|
|
||||||
private class EroticFilter : Filter.TriState("Erótico")
|
private class EroticFilter : Filter.TriState("Erótico")
|
||||||
|
|
||||||
private class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
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(
|
||||||
@ -350,7 +312,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
// 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://tumangaonline.me/library
|
||||||
// Last revision 13/04/2020
|
// Last revision 13/07/2020
|
||||||
private fun getGenreList() = listOf(
|
private fun getGenreList() = listOf(
|
||||||
Genre("Acción", "1"),
|
Genre("Acción", "1"),
|
||||||
Genre("Aventura", "2"),
|
Genre("Aventura", "2"),
|
||||||
@ -407,8 +369,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
fun toUriPart() = vals[state].second
|
fun toUriPart() = vals[state].second
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preferences Code
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
@ -417,7 +377,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val deduppref = androidx.preference.ListPreference(screen.context).apply {
|
val deduppref = androidx.preference.ListPreference(screen.context).apply {
|
||||||
key = DEDUP_PREF_Title
|
key = DEDUP_PREF_Title
|
||||||
title = DEDUP_PREF_Title
|
title = DEDUP_PREF_Title
|
||||||
entries = arrayOf("All scanlators", "One scanlator per chapter")
|
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator")
|
||||||
entryValues = arrayOf("all", "one")
|
entryValues = arrayOf("all", "one")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -432,7 +392,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val pageMethod = androidx.preference.ListPreference(screen.context).apply {
|
val pageMethod = androidx.preference.ListPreference(screen.context).apply {
|
||||||
key = PAGEGET_PREF_Title
|
key = PAGEGET_PREF_Title
|
||||||
title = PAGEGET_PREF_Title
|
title = PAGEGET_PREF_Title
|
||||||
entries = arrayOf("Cascada", "Paginada")
|
entries = arrayOf("Cascada (recomendado)", "Paginado")
|
||||||
entryValues = arrayOf("cascade", "paginated")
|
entryValues = arrayOf("cascade", "paginated")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -452,7 +412,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val deduppref = ListPreference(screen.context).apply {
|
val deduppref = ListPreference(screen.context).apply {
|
||||||
key = DEDUP_PREF_Title
|
key = DEDUP_PREF_Title
|
||||||
title = DEDUP_PREF_Title
|
title = DEDUP_PREF_Title
|
||||||
entries = arrayOf("All scanlators", "One scanlator per chapter")
|
entries = arrayOf("Mostrar todos los scanlators", "Mostrar solo un scanlator")
|
||||||
entryValues = arrayOf("all", "one")
|
entryValues = arrayOf("all", "one")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -467,7 +427,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
val pageMethod = ListPreference(screen.context).apply {
|
val pageMethod = ListPreference(screen.context).apply {
|
||||||
key = PAGEGET_PREF_Title
|
key = PAGEGET_PREF_Title
|
||||||
title = PAGEGET_PREF_Title
|
title = PAGEGET_PREF_Title
|
||||||
entries = arrayOf("Cascada", "Paginada")
|
entries = arrayOf("Cascada (recomendado)", "Paginado")
|
||||||
entryValues = arrayOf("cascade", "paginated")
|
entryValues = arrayOf("cascade", "paginated")
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
|
|
||||||
@ -484,12 +444,13 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getduppref() = preferences.getString(DEDUP_PREF, "all")
|
private fun getduppref() = preferences.getString(DEDUP_PREF, "all")
|
||||||
|
|
||||||
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade")
|
private fun getPageMethod() = preferences.getString(PAGEGET_PREF, "cascade")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DEDUP_PREF_Title = "Chapter List Scanlator Preference"
|
private const val DEDUP_PREF_Title = "Preferencias de scanlator"
|
||||||
private const val DEDUP_PREF = "deduppref"
|
private const val DEDUP_PREF = "deduppref"
|
||||||
private const val PAGEGET_PREF_Title = "Método para obtener imágenes"
|
private const val PAGEGET_PREF_Title = "Método para la descarga de imágenes"
|
||||||
private const val PAGEGET_PREF = "pagemethodpref"
|
private const val PAGEGET_PREF = "pagemethodpref"
|
||||||
|
|
||||||
private val SORTABLES = listOf(
|
private val SORTABLES = listOf(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user