Update Lector (#2447)

Update Lector
This commit is contained in:
happywillow0 2020-03-18 20:30:14 -04:00 committed by GitHub
parent f93f28cbd4
commit ee394dc2a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 187 additions and 162 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: LectorManga' appName = 'Tachiyomi: LectorManga'
pkgNameSuffix = 'es.lectormanga' pkgNameSuffix = 'es.lectormanga'
extClass = '.LectorManga' extClass = '.LectorManga'
extVersionCode = 9 extVersionCode = 10
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -7,16 +7,28 @@ import android.support.v7.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor 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.* import eu.kanade.tachiyomi.source.model.Filter
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.* import okhttp3.FormBody
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.Date
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -24,26 +36,24 @@ import java.util.concurrent.TimeUnit
*/ */
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 rateLimitInterceptor = RateLimitInterceptor(1) //Client
private val rateLimitInterceptor = RateLimitInterceptor(1)
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor) .addNetworkInterceptor(rateLimitInterceptor)
.connectTimeout(1, TimeUnit.MINUTES) .connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true) .retryOnConnectionFailure(true)
.followRedirects(true) .followRedirects(true)
.build()!! .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()
.add("User-Agent", userAgent) .add("User-Agent", userAgent)
@ -54,7 +64,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
val req = Request.Builder() val req = Request.Builder()
.headers(headers) .headers(headers)
.url(url) .url(url)
.method(method,formBody) .method(method, formBody)
.build() .build()
return client.newCall(req) return client.newCall(req)
@ -64,24 +74,23 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
.toString() .toString()
} }
private val preferences: SharedPreferences by lazy { //Popular
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularMangaSelector() = ".col-6 .card"
override fun latestUpdatesSelector() = "div.table-responsive:first-child td[scope=row]:nth-child(5n-3)"
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 latestUpdatesRequest(page: Int) = GET(baseUrl, headers) 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, headers)
override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesSelector() = "div.table-responsive:first-child td[scope=row]:nth-child(5n-3)"
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector()) val mangas = document.select(latestUpdatesSelector())
@ -99,25 +108,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
return manga return manga
} }
override fun mangaDetailsParse(document: Document) = SManga.create().apply { //Search
genre = document.select("a.py-2").joinToString(", ") {
it.text()
}
description = document.select(".col-12.mt-2")?.text()
status = parseStatus(document.select(".status-publishing")?.text().orEmpty())
thumbnail_url = document.select(".text-center img.img-fluid").attr("src")
}
private fun parseStatus(status: String) = when {
status.contains("Publicándose") -> SManga.ONGOING
status.contains("Finalizado") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
override fun popularMangaNextPageSelector() = ".pagination .page-item:not(.disabled) a[rel='next']"
override fun latestUpdatesNextPageSelector() = "none"
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()
@ -182,21 +173,37 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() //Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
genre = document.select("a.py-2").joinToString(", ") {
it.text()
}
description = document.select(".col-12.mt-2")?.text()
status = parseStatus(document.select(".status-publishing")?.text().orEmpty())
thumbnail_url = document.select(".text-center img.img-fluid").attr("src")
}
private fun parseStatus(status: String) = when {
status.contains("Publicándose") -> SManga.ONGOING
status.contains("Finalizado") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
//Chapters
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
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()
val chapterurl = response.request().url().toString() val chapterUrl = response.request().url().toString()
val chapteridselector = ""
// One-shot // One-shot
if (document.select("#chapters").isEmpty()) { if (document.select("#chapters").isEmpty()) {
return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it, chapterurl, chapteridselector) } return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it, chapterUrl) }
} }
// Regular list of chapters // Regular list of chapters
@ -207,10 +214,10 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
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, chapteridselector)) } scanlator.last { chapters.add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
} else { } else {
scanlator.forEach { chapters.add(regularChapterFromElement(chapterNames[index].text(), it ,chapterNumbers[index], chapterurl, chapteridselector)) } scanlator.forEach { chapters.add(regularChapterFromElement(chapterNames[index].text(), it, chapterNumbers[index], chapterUrl)) }
} }
} }
return chapters return chapters
@ -221,37 +228,46 @@ class LectorManga : 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, chapteridselector: String) = SChapter.create().apply { private fun oneShotChapterFromElement(element: Element, chapterUrl: String) = SChapter.create().apply {
url = "$chapterurl#${element.select("div.row > .text-right > form").attr("id")}" url = "$chapterUrl#${element.select("div.row > .text-right > form").attr("id")}"
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) } ?: 0 date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) }
?: 0
} }
private fun regularChapterFromElement(chapterName: String, info: Element, number: Float, chapterurl: String, chapteridselector: String): SChapter { private fun regularChapterFromElement(chapterName: String, info: Element, number: Float, chapterUrl: String): SChapter {
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.url = "$chapterurl#${info.select("div.row > .text-right > form").attr("id")}" chapter.url = "$chapterUrl#${info.select("div.row > .text-right > form").attr("id")}"
chapter.name = chapterName chapter.name = chapterName
chapter.scanlator = info.select("div.col-md-6.text-truncate")?.text() chapter.scanlator = info.select("div.col-md-6.text-truncate")?.text()
chapter.date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) }
?: 0
chapter.chapter_number = number chapter.chapter_number = number
return chapter return chapter
} }
private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time ?: 0 private fun parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time
?: 0
//Utilities
private var time = serverTime() //Grab time at app launch, can be updated private var time = serverTime() //Grab time at app launch, can be updated
private fun serverTime() :String { private fun serverTime(): String {
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
formatter.timeZone = TimeZone.getTimeZone("GMT+1") //Convert time to match server formatter.timeZone = TimeZone.getTimeZone("GMT+1") //Convert time to match server
return formatter.format(Date()) return formatter.format(Date())
} }
//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
@ -270,10 +286,12 @@ 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)
val url = if (getPageMethod()=="cascade" && newurl.contains("paginated")) {
// Get /cascade instead of /paginate to get all pages at once
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
@ -282,15 +300,16 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
.add("Referer", newurl) .add("Referer", newurl)
.build() .build()
// Get /cascade instead of /paginate to get all pages at once
return GET(url, headers) return GET(url, headers)
} }
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply { override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
if (getPageMethod()=="cascade") { if (getPageMethod() == "cascade") {
val style = document.select("style:containsData(height)").html() document.select("img.viewer-img").forEach {
document.select( "img[id]").filterNot { "#${it.attr("id")}," in style || "#${it.attr("id")}{" in style }.forEach { add(Page(size, "", it.let {
add(Page(size, "", it.attr("src"))) if (it.hasAttr("data-src"))
it.attr("abs:data-src") else it.attr("abs:src")
}))
} }
} else { } else {
val pageList = document.select("#viewer-pages-select").first().select("option").map { it.attr("value").toInt() } val pageList = document.select("#viewer-pages-select").first().select("option").map { it.attr("value").toInt() }
@ -303,6 +322,8 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
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("Tipo", arrayOf( private class Types : UriPartFilter("Tipo", arrayOf(
Pair("Ver todo", ""), Pair("Ver todo", ""),
Pair("Manga", "manga"), Pair("Manga", "manga"),
@ -423,7 +444,11 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
// Preferences Code // Preferences
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val deduppref = androidx.preference.ListPreference(screen.context).apply { val deduppref = androidx.preference.ListPreference(screen.context).apply {
@ -436,7 +461,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = this.findIndexOfValue(selected) val index = this.findIndexOfValue(selected)
val entry = entryValues.get(index) as String val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit() preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
@ -451,7 +476,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = this.findIndexOfValue(selected) val index = this.findIndexOfValue(selected)
val entry = entryValues.get(index) as String val entry = entryValues[index] as String
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }
@ -470,7 +495,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = this.findIndexOfValue(selected) val index = this.findIndexOfValue(selected)
val entry = entryValues.get(index) as String val entry = entryValues[index] as String
preferences.edit().putString(DEDUP_PREF, entry).commit() preferences.edit().putString(DEDUP_PREF, entry).commit()
} }
} }
@ -485,7 +510,7 @@ class LectorManga : ConfigurableSource, ParsedHttpSource() {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String val selected = newValue as String
val index = this.findIndexOfValue(selected) val index = this.findIndexOfValue(selected)
val entry = entryValues.get(index) as String val entry = entryValues[index] as String
preferences.edit().putString(PAGEGET_PREF, entry).commit() preferences.edit().putString(PAGEGET_PREF, entry).commit()
} }
} }