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:
Edgar Mejía 2020-07-13 18:47:50 -06:00 committed by GitHub
parent 4f9a63317e
commit 8ef7624233
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 185 deletions

View File

@ -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'
} }

View File

@ -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(

View File

@ -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'
} }

View File

@ -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(