Update TuMangaOnline, LectorManga & TMOHentai (#2656)

* Update filters, update latest URL to prevent server ban for too many requests.

* Update TMO.

* Update filters, fix latest updates URL.
This commit is contained in:
Edgar Mejía 2020-04-13 15:22:39 -06:00 committed by GitHub
parent 7357093dde
commit 8b120ef5d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 206 deletions

View File

@ -5,12 +5,11 @@ ext {
appName = 'Tachiyomi: LectorManga' appName = 'Tachiyomi: LectorManga'
pkgNameSuffix = 'es.lectormanga' pkgNameSuffix = 'es.lectormanga'
extClass = '.LectorManga' extClass = '.LectorManga'
extVersionCode = 10 extVersionCode = 11
libVersion = '1.2' libVersion = '1.2'
} }
dependencies { dependencies {
implementation project(':lib-ratelimit')
compileOnly project(':preference-stub') compileOnly project(':preference-stub')
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440' compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
} }

View File

@ -2,34 +2,19 @@ package eu.kanade.tachiyomi.extension.es.lectormanga
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.ListPreference import android.support.v7.preference.*
import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody import okhttp3.*
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.*
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
/** /**
* Note: this source is similar to TuMangaOnline. * Note: this source is similar to TuMangaOnline.
@ -43,16 +28,6 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
override val lang = "es" override val lang = "es"
override val supportsLatest = true override val supportsLatest = true
//Client
private val rateLimitInterceptor = RateLimitInterceptor(1)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true)
.followRedirects(true)
.build()
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()
@ -88,25 +63,10 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
//Latest //Latest
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&page=$page", headers)
override fun latestUpdatesNextPageSelector(): String? = null override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = "div.table-responsive:first-child td[scope=row]:nth-child(5n-3)" override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector())
.distinctBy { it.select("td").text().trim() }
.map { latestUpdatesFromElement(it) }
val hasNextPage = false
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create()
manga.setUrlWithoutDomain(element.select("a").first().attr("href"))
manga.title = element.select("td").text().trim()
return manga
}
//Search //Search
@ -127,11 +87,14 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
is FilterBy -> { is FilterBy -> {
url.addQueryParameter("filter_by", filter.toUriPart()) url.addQueryParameter("filter_by", filter.toUriPart())
} }
is OrderBy -> { is SortBy -> {
url.addQueryParameter("order_item", filter.toUriPart()) if (filter.state != null) {
url.addQueryParameter("order_item", SORTABLES[filter.state!!.index].second)
url.addQueryParameter(
"order_dir",
if (filter.state!!.ascending) { "asc" } else { "desc" }
)
} }
is OrderDir -> {
url.addQueryParameter("order_dir", filter.toUriPart())
} }
is WebcomicFilter -> { is WebcomicFilter -> {
url.addQueryParameter("webcomic", when (filter.state) { url.addQueryParameter("webcomic", when (filter.state) {
@ -195,7 +158,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
//Chapters //Chapters
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> = mutableListOf<SChapter>().apply {
time = serverTime() //Get time when the chapter page is opened time = serverTime() //Get time when the chapter page is opened
val document = response.asJsoup() val document = response.asJsoup()
@ -207,20 +170,19 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
} }
// Regular list of chapters // Regular list of chapters
val chapters = mutableListOf<SChapter>()
val dupselect = getduppref()!! val dupselect = getduppref()!!
val chapterNames = document.select("#chapters h4.text-truncate") val chapterNames = document.select("#chapters h4.text-truncate")
val chapterNumbers = chapterNames.map { it.text().substringAfter("Capítulo").substringBefore("|").trim().toFloat() } val chapterNumbers = chapterNames.map { it.text().substringAfter("Capítulo").substringBefore("|").trim().toFloat() }
val chapterInfos = document.select("#chapters .chapter-list") val chapterInfos = document.select("#chapters .chapter-list")
chapterNames.forEachIndexed { index, _ -> chapterNames.forEachIndexed { index, _ ->
val scanlator = chapterInfos[index].select("li") val scanlator = chapterInfos[index].select("li")
if (dupselect == "one") { if (dupselect == "one") {
scanlator.last { chapters.add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) } scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
} else { } else {
scanlator.forEach { chapters.add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) } scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
} }
} }
return chapters
} }
override fun chapterListSelector() = throw UnsupportedOperationException("Not used") override fun chapterListSelector() = throw UnsupportedOperationException("Not used")
@ -236,19 +198,20 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
?: 0 ?: 0
} }
private fun regularChapterFromElement(chapterName: String, info: Element, number: Float, chapterUrl: String): SChapter { private fun regularChapterFromElement(chapterName: String, info: Element, number: Float, chapterUrl: String) = SChapter.create().apply {
val chapter = SChapter.create() url = "$chapterUrl#${info.select("div.row > .text-right > form").attr("id")}"
chapter.url = "$chapterUrl#${info.select("div.row > .text-right > form").attr("id")}" name = chapterName
chapter.name = chapterName scanlator = info.select("div.col-md-6.text-truncate")?.text()
chapter.scanlator = info.select("div.col-md-6.text-truncate")?.text() date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let {
chapter.date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } parseChapterDate(it)
?: 0 } ?: 0
chapter.chapter_number = number chapter_number = number
return chapter
} }
private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time private fun parseChapterDate(date: String): Long {
?: 0 return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
.parse(date)?.time ?: 0
}
//Utilities //Utilities
@ -262,12 +225,11 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
//Pages //Pages
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val (chapterURL, chapterID) = chapter.url.split("#") val (chapterURL, chapterID) = chapter.url.split("#")
val response = client.newCall(GET(chapterURL, headers)).execute() //Get chapter page for current token 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()}") if (!response.isSuccessful) throw Exception("Lector Manga HTTP Error ${response.code()}")
val document = response.asJsoup() val document = response.asJsoup()
val geturl = document.select("form#$chapterID").attr("action") + "/$time" //Get redirect URL val getUrl = document.select("form#$chapterID").attr("action") + "/$time" //Get redirect URL
val token = document.select("form#$chapterID input").attr("value") //Get token val token = document.select("form#$chapterID input").attr("value") //Get token
val method = document.select("form#$chapterID").attr("method") //Check POST or GET val method = document.select("form#$chapterID").attr("method") //Check POST or GET
time = serverTime() //Update time for next chapter time = serverTime() //Update time for next chapter
@ -286,18 +248,18 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
else -> throw UnsupportedOperationException("Lector Manga something else broke.") else -> throw UnsupportedOperationException("Lector Manga something else broke.")
} }
val newurl = getBuilder(geturl, getHeaders, formBody, method) 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 url = if (getPageMethod() == "cascade" && newUrl.contains("paginated")) {
newurl.substringBefore("paginated") + "cascade" newUrl.substringBefore("paginated") + "cascade"
} else if (getPageMethod() == "paginated" && newurl.contains("cascade")) { } else if (getPageMethod() == "paginated" && newUrl.contains("cascade")) {
newurl.substringBefore("cascade") + "paginated" newUrl.substringBefore("cascade") + "paginated"
} else newurl } else newUrl
val headers = headersBuilder() val headers = headersBuilder()
.add("User-Agent", userAgent) .add("User-Agent", userAgent)
.add("Referer", newurl) .add("Referer", newUrl)
.build() .build()
return GET(url, headers) return GET(url, headers)
@ -324,7 +286,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
//Filters //Filters
private class Types : UriPartFilter("Tipo", arrayOf( private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
Pair("Ver todo", ""), Pair("Ver todo", ""),
Pair("Manga", "manga"), Pair("Manga", "manga"),
Pair("Manhua", "manhua"), Pair("Manhua", "manhua"),
@ -335,7 +297,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
Pair("Oel", "oel") Pair("Oel", "oel")
)) ))
private class Demography : UriPartFilter("Demografía", arrayOf( private class Demography : UriPartFilter("Filtrar por demografía", arrayOf(
Pair("Ver todo", ""), Pair("Ver todo", ""),
Pair("Seinen", "seinen"), Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"), Pair("Shoujo", "shoujo"),
@ -344,24 +306,17 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
Pair("Kodomo", "kodomo") Pair("Kodomo", "kodomo")
)) ))
private class FilterBy : UriPartFilter("Ordenar por", arrayOf( private class FilterBy : UriPartFilter("Campo de orden", arrayOf(
Pair("Título", "title"), Pair("Título", "title"),
Pair("Autor", "author"), Pair("Autor", "author"),
Pair("Compañia", "company") Pair("Compañia", "company")
)) ))
private class OrderBy : UriPartFilter("Ordenar por", arrayOf( class SortBy : Filter.Sort(
Pair("Me gusta", "likes_count"), "Ordenar por",
Pair("Alfabético", "alphabetically"), SORTABLES.map { it.first }.toTypedArray(),
Pair("Puntuación", "score"), Selection(0, false)
Pair("Creación", "creation"), )
Pair("Fecha estreno", "release_date")
))
private class OrderDir : UriPartFilter("Ordenar por", arrayOf(
Pair("DESC", "desc"),
Pair("ASC", "asc")
))
private class WebcomicFilter : Filter.TriState("Webcomic") private class WebcomicFilter : Filter.TriState("Webcomic")
private class FourKomaFilter : Filter.TriState("Yonkoma") private class FourKomaFilter : Filter.TriState("Yonkoma")
@ -369,15 +324,14 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
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>("Géneros", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Types(), Types(),
Demography(), Demography(),
Filter.Separator(), Filter.Separator(),
FilterBy(), FilterBy(),
OrderBy(), SortBy(),
OrderDir(),
Filter.Separator(), Filter.Separator(),
WebcomicFilter(), WebcomicFilter(),
FourKomaFilter(), FourKomaFilter(),
@ -386,8 +340,10 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
GenreList(getGenreList()) GenreList(getGenreList())
) )
// Array.from(document.querySelectorAll('#advancedSearch .custom-checkbox')).map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') // Array.from(document.querySelectorAll('#advancedSearch .custom-checkbox'))
// .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
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Acción", "1"), Genre("Acción", "1"),
Genre("Aventura", "2"), Genre("Aventura", "2"),
@ -480,6 +436,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }
screen.addPreference(deduppref) screen.addPreference(deduppref)
screen.addPreference(pageMethod) screen.addPreference(pageMethod)
} }
@ -514,19 +471,26 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }
screen.addPreference(deduppref) screen.addPreference(deduppref)
screen.addPreference(pageMethod) screen.addPreference(pageMethod)
} }
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 = "Chapter List Scanlator Preference"
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 obtener imágenes"
private const val PAGEGET_PREF = "pagemethodpref" private const val PAGEGET_PREF = "pagemethodpref"
private val SORTABLES = listOf(
Pair("Me gusta", "likes_count"),
Pair("Alfabético", "alphabetically"),
Pair("Puntuación", "score"),
Pair("Creación", "creation"),
Pair("Fecha estreno", "release_date")
)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: TMOHentai' appName = 'Tachiyomi: TMOHentai'
pkgNameSuffix = 'es.tmohentai' pkgNameSuffix = 'es.tmohentai'
extClass = '.TMOHentai' extClass = '.TMOHentai'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -17,7 +17,7 @@ class TMOHentai : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaRequest(page: Int) = GET("$baseUrl/section/hentai?view=list&page=$page&order=publication_date&order-dir=asc&search%5BsearchText%5D=&search%5BsearchBy%5D=name&type=all", headers) override fun popularMangaRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=popularity&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers)
override fun popularMangaSelector() = "table > tbody > tr[data-toggle=popover]" override fun popularMangaSelector() = "table > tbody > tr[data-toggle=popover]"
@ -31,7 +31,7 @@ class TMOHentai : ParsedHttpSource() {
override fun popularMangaNextPageSelector() = "a[rel=next]" override fun popularMangaNextPageSelector() = "a[rel=next]"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filter/all?view=list&page=$page") override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/section/all?view=list&page=$page&order=publication_date&order-dir=desc&search[searchText]=&search[searchBy]=name&type=all", headers)
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
@ -75,10 +75,9 @@ class TMOHentai : ParsedHttpSource() {
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used") override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/section/hentai?view=list")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/section/all?view=list")!!.newBuilder()
url.addQueryParameter("search[searchText]", query) url.addQueryParameter("search[searchText]", query)
url.addQueryParameter("search[searchBy]", "name")
url.addQueryParameter("page", page.toString()) url.addQueryParameter("page", page.toString())
filters.forEach { filter -> filters.forEach { filter ->
@ -86,17 +85,23 @@ class TMOHentai : ParsedHttpSource() {
is Types -> { is Types -> {
url.addQueryParameter("type", filter.toUriPart()) url.addQueryParameter("type", filter.toUriPart())
} }
is OrderBy -> {
url.addQueryParameter("order", filter.toUriPart())
}
is OrderDir -> {
url.addQueryParameter("order-dir", filter.toUriPart())
}
is GenreList -> { is GenreList -> {
filter.state filter.state
.filter { genre -> genre.state } .filter { genre -> genre.state }
.forEach { genre -> url.addQueryParameter("genders[]", genre.id) } .forEach { genre -> url.addQueryParameter("genders[]", genre.id) }
} }
is FilterBy -> {
url.addQueryParameter("search[searchBy]", filter.toUriPart())
}
is SortBy -> {
if (filter.state != null) {
url.addQueryParameter("order", SORTABLES[filter.state!!.index].second)
url.addQueryParameter(
"order-dir",
if (filter.state!!.ascending) { "asc" } else { "desc" }
)
}
}
} }
} }
@ -110,13 +115,14 @@ class TMOHentai : ParsedHttpSource() {
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
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>("Géneros", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Géneros", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Types(), Types(),
Filter.Separator(), Filter.Separator(),
OrderBy(), FilterBy(),
OrderDir(), SortBy(),
Filter.Separator(), Filter.Separator(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
@ -126,7 +132,7 @@ class TMOHentai : ParsedHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
private class Types : UriPartFilter("Tipo", arrayOf( private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
Pair("Ver todos", "all"), Pair("Ver todos", "all"),
Pair("Manga", "hentai"), Pair("Manga", "hentai"),
Pair("Light Hentai", "light-hentai"), Pair("Light Hentai", "light-hentai"),
@ -135,18 +141,21 @@ class TMOHentai : ParsedHttpSource() {
Pair("Other", "otro") Pair("Other", "otro")
)) ))
private class OrderBy : UriPartFilter("Ordenar por", arrayOf( private class FilterBy : UriPartFilter("Campo de orden", arrayOf(
Pair("Alfabético", "alphabetic"), Pair("Nombre", "name"),
Pair("Creación", "publication_date"), Pair("Artista", "artist"),
Pair("Popularidad", "popularity") Pair("Revista", "magazine"),
Pair("Tag", "tag")
)) ))
private class OrderDir : UriPartFilter("Ordenar por", arrayOf( class SortBy : Filter.Sort(
Pair("ASC", "asc"), "Ordenar por",
Pair("DESC", "desc") SORTABLES.map { it.first }.toTypedArray(),
)) Selection(2, false)
)
// Array.from(document.querySelectorAll('#advancedSearch .list-group .list-group-item')).map(a => `Genre("${a.querySelector('span').innerText.replace(' ', '')}", "${a.querySelector('input').value}")`).join(',\n') // Array.from(document.querySelectorAll('#advancedSearch .list-group .list-group-item'))
// .map(a => `Genre("${a.querySelector('span').innerText.replace(' ', '')}", "${a.querySelector('input').value}")`).join(',\n')
// https://tmohentai.com/section/hentai // https://tmohentai.com/section/hentai
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Romance", "1"), Genre("Romance", "1"),
@ -196,4 +205,13 @@ class TMOHentai : ParsedHttpSource() {
Genre("Tsundere", "45"), Genre("Tsundere", "45"),
Genre("Yandere", "46") Genre("Yandere", "46")
) )
companion object {
private val SORTABLES = listOf(
Pair("Alfabético", "alphabetic"),
Pair("Creación", "publication_date"),
Pair("Popularidad", "popularity")
)
}
} }

View File

@ -5,12 +5,11 @@ ext {
appName = 'Tachiyomi: TuMangaOnline' appName = 'Tachiyomi: TuMangaOnline'
pkgNameSuffix = 'es.tumangaonline' pkgNameSuffix = 'es.tumangaonline'
extClass = '.TuMangaOnline' extClass = '.TuMangaOnline'
extVersionCode = 25 extVersionCode = 26
libVersion = '1.2' libVersion = '1.2'
} }
dependencies { dependencies {
implementation project(':lib-ratelimit')
compileOnly project(':preference-stub') compileOnly project(':preference-stub')
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440' compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
} }

View File

@ -4,30 +4,18 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.ListPreference import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody import okhttp3.*
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
@ -40,14 +28,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
//Client //Client
private val rateLimitInterceptor = RateLimitInterceptor(1, 1)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true)
.followRedirects(true)
.build()!!
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()
@ -61,7 +41,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
.headers(headers) .headers(headers)
.url(url) .url(url)
.method(method, formBody) .method(method, formBody)
//.post(formBody)
.build() .build()
return client.newCall(req) return client.newCall(req)
@ -69,9 +48,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
.request() .request()
.url() .url()
.toString() .toString()
/*.execute()
.body()!!
.string()*/
} }
//Popular //Popular
@ -89,27 +65,10 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
//Latest //Latest
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest_uploads?page=$page&uploads_mode=thumbnail", 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 latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = "div.upload-file-row" override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector())
.distinctBy { it.select("div.thumbnail-title > h4.text-truncate").text().trim() }
.map { latestUpdatesFromElement(it) }
val hasNextPage = latestUpdatesNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
element.select("div.upload-file-row > a").let {
setUrlWithoutDomain(it.attr("href"))
title = it.select("div.thumbnail-title > h4.text-truncate").text()
thumbnail_url = it.select("div.thumbnail > style").toString().substringAfter("url('").substringBefore("');")
}
}
//Search //Search
@ -131,11 +90,14 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
is FilterBy -> { is FilterBy -> {
url.addQueryParameter("filter_by", filter.toUriPart()) url.addQueryParameter("filter_by", filter.toUriPart())
} }
is OrderBy -> { is SortBy -> {
url.addQueryParameter("order_item", filter.toUriPart()) if (filter.state != null) {
url.addQueryParameter("order_item", SORTABLES[filter.state!!.index].second)
url.addQueryParameter(
"order_dir",
if (filter.state!!.ascending) { "asc" } else { "desc" }
)
} }
is OrderDir -> {
url.addQueryParameter("order_dir", filter.toUriPart())
} }
is WebcomicFilter -> { is WebcomicFilter -> {
url.addQueryParameter("webcomic", when (filter.state) { url.addQueryParameter("webcomic", when (filter.state) {
@ -217,7 +179,11 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
// Regular list of chapters // Regular list of chapters
val chapters = mutableListOf<SChapter>() val chapters = mutableListOf<SChapter>()
document.select(regularChapterListSelector()).forEach { chapelement -> document.select(regularChapterListSelector()).forEach { chapelement ->
val chapternumber = chapelement.select("a.btn-collapse").text().substringBefore(":").substringAfter("Capítulo").trim().toFloat() val chapternumber = chapelement.select("a.btn-collapse").text()
.substringBefore(":")
.substringAfter("Capítulo")
.trim()
.toFloat()
val chaptername = chapelement.select("div.col-10.text-truncate").text() val chaptername = chapelement.select("div.col-10.text-truncate").text()
val scanelement = chapelement.select("ul.chapter-list > li") val scanelement = chapelement.select("ul.chapter-list > li")
val dupselect = getduppref()!! val dupselect = getduppref()!!
@ -231,6 +197,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
} }
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"
@ -318,7 +285,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
//Filters //Filters
private class Types : UriPartFilter("Tipo", arrayOf( private class Types : UriPartFilter("Filtrar por tipo", arrayOf(
Pair("Ver todo", ""), Pair("Ver todo", ""),
Pair("Manga", "manga"), Pair("Manga", "manga"),
Pair("Manhua", "manhua"), Pair("Manhua", "manhua"),
@ -329,7 +296,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
Pair("Oel", "oel") Pair("Oel", "oel")
)) ))
private class Demography : UriPartFilter("Demografía", arrayOf( private class Demography : UriPartFilter("Filtrar por demografía", arrayOf(
Pair("Ver todo", ""), Pair("Ver todo", ""),
Pair("Seinen", "seinen"), Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"), Pair("Shoujo", "shoujo"),
@ -338,24 +305,17 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
Pair("Kodomo", "kodomo") Pair("Kodomo", "kodomo")
)) ))
private class FilterBy : UriPartFilter("Ordenar por", arrayOf( private class FilterBy : UriPartFilter("Campo de orden", arrayOf(
Pair("Título", "title"), Pair("Título", "title"),
Pair("Autor", "author"), Pair("Autor", "author"),
Pair("Compañia", "company") Pair("Compañia", "company")
)) ))
private class OrderBy : UriPartFilter("Ordenar por", arrayOf( class SortBy : Filter.Sort(
Pair("Me gusta", "likes_count"), "Ordenar por",
Pair("Alfabético", "alphabetically"), SORTABLES.map { it.first }.toTypedArray(),
Pair("Puntuación", "score"), Selection(0, false)
Pair("Creación", "creation"), )
Pair("Fecha estreno", "release_date")
))
private class OrderDir : UriPartFilter("Ordenar por", arrayOf(
Pair("ASC", "asc"),
Pair("DESC", "desc")
))
private class WebcomicFilter : Filter.TriState("Webcomic") private class WebcomicFilter : Filter.TriState("Webcomic")
private class FourKomaFilter : Filter.TriState("Yonkoma") private class FourKomaFilter : Filter.TriState("Yonkoma")
@ -363,15 +323,14 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
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>("Géneros", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Types(), Types(),
Demography(), Demography(),
Filter.Separator(), Filter.Separator(),
FilterBy(), FilterBy(),
OrderBy(), SortBy(),
OrderDir(),
Filter.Separator(), Filter.Separator(),
WebcomicFilter(), WebcomicFilter(),
FourKomaFilter(), FourKomaFilter(),
@ -380,8 +339,10 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
GenreList(getGenreList()) GenreList(getGenreList())
) )
// Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control')).map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') // Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control'))
// .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
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Acción", "1"), Genre("Acción", "1"),
Genre("Aventura", "2"), Genre("Aventura", "2"),
@ -474,6 +435,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }
screen.addPreference(deduppref) screen.addPreference(deduppref)
screen.addPreference(pageMethod) screen.addPreference(pageMethod)
} }
@ -508,19 +470,27 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() {
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }
screen.addPreference(deduppref) screen.addPreference(deduppref)
screen.addPreference(pageMethod) screen.addPreference(pageMethod)
} }
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 = "Chapter List Scanlator Preference"
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 obtener imágenes"
private const val PAGEGET_PREF = "pagemethodpref" private const val PAGEGET_PREF = "pagemethodpref"
private val SORTABLES = listOf(
Pair("Me gusta", "likes_count"),
Pair("Alfabético", "alphabetically"),
Pair("Puntuación", "score"),
Pair("Creación", "creation"),
Pair("Fecha estreno", "release_date"),
Pair("Núm. Capítulos", "num_chapters")
)
} }
} }