From ac1575dd815ac15bca331e85ae441bade26de0e3 Mon Sep 17 00:00:00 2001 From: bapeey <90949336+bapeey@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:43:21 -0500 Subject: [PATCH] Move TMO and LectorManga to multisrc (#1002) * Theme-lib when * Lint * Order generator * Apply no-nsfw pref on request instead of init --- .../lectortmo/default}/AndroidManifest.xml | 7 +- .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../lectortmo/lectormanga/src/LectorManga.kt | 72 ++ .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../tumangaonline/src/TuMangaOnline.kt | 8 + .../tachiyomi/multisrc/lectortmo/LectorTmo.kt | 450 ++++++------ .../multisrc/lectortmo/LectorTmoFilters.kt | 16 + .../multisrc/lectortmo/LectorTmoGenerator.kt | 25 + .../lectortmo/LectorTmoUrlActivity.kt | 14 +- src/es/lectormanga/AndroidManifest.xml | 23 - src/es/lectormanga/build.gradle | 8 - .../extension/es/lectormanga/LectorManga.kt | 660 ------------------ .../es/lectormanga/LectorMangaUrlActivity.kt | 39 -- src/es/tumangaonline/build.gradle | 8 - 22 files changed, 330 insertions(+), 1000 deletions(-) rename {src/es/tumangaonline => multisrc/overrides/lectortmo/default}/AndroidManifest.xml (78%) rename {src/es => multisrc/overrides/lectortmo}/lectormanga/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/lectormanga/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/lectormanga/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/lectormanga/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png (100%) create mode 100644 multisrc/overrides/lectortmo/lectormanga/src/LectorManga.kt rename {src/es => multisrc/overrides/lectortmo}/tumangaonline/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/tumangaonline/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/tumangaonline/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/es => multisrc/overrides/lectortmo}/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png (100%) create mode 100644 multisrc/overrides/lectortmo/tumangaonline/src/TuMangaOnline.kt rename src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt => multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmo.kt (81%) create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoFilters.kt create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoGenerator.kt rename src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt => multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoUrlActivity.kt (65%) delete mode 100644 src/es/lectormanga/AndroidManifest.xml delete mode 100644 src/es/lectormanga/build.gradle delete mode 100644 src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt delete mode 100644 src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt delete mode 100644 src/es/tumangaonline/build.gradle diff --git a/src/es/tumangaonline/AndroidManifest.xml b/multisrc/overrides/lectortmo/default/AndroidManifest.xml similarity index 78% rename from src/es/tumangaonline/AndroidManifest.xml rename to multisrc/overrides/lectortmo/default/AndroidManifest.xml index ec978927b..b9d3760ca 100644 --- a/src/es/tumangaonline/AndroidManifest.xml +++ b/multisrc/overrides/lectortmo/default/AndroidManifest.xml @@ -3,7 +3,7 @@ @@ -12,11 +12,10 @@ - + android:scheme="${SOURCESCHEME}" /> diff --git a/src/es/lectormanga/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/lectortmo/lectormanga/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/es/lectormanga/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/lectormanga/res/mipmap-hdpi/ic_launcher.png diff --git a/src/es/lectormanga/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/lectortmo/lectormanga/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/es/lectormanga/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/lectormanga/res/mipmap-mdpi/ic_launcher.png diff --git a/src/es/lectormanga/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/lectormanga/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/es/lectormanga/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/lectormanga/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/es/lectormanga/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/lectormanga/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/es/lectormanga/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/lectormanga/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/es/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/es/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/lectormanga/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/lectortmo/lectormanga/src/LectorManga.kt b/multisrc/overrides/lectortmo/lectormanga/src/LectorManga.kt new file mode 100644 index 000000000..7004a6be0 --- /dev/null +++ b/multisrc/overrides/lectortmo/lectormanga/src/LectorManga.kt @@ -0,0 +1,72 @@ +package eu.kanade.tachiyomi.extension.es.lectormanga + +import eu.kanade.tachiyomi.multisrc.lectortmo.LectorTmo +import eu.kanade.tachiyomi.network.GET +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.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class LectorManga : LectorTmo("LectorManga", "https://lectormanga.com", "es") { + + override val id = 7925520943983324764 + + override fun popularMangaSelector() = ".col-6 .card" + + override fun popularMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.select("a").attr("href")) + title = element.select("a").text() + thumbnail_url = element.select("img").attr("src") + } + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + document.selectFirst("h1:has(small)")?.let { title = it.ownText() } + genre = document.select("a.py-2").joinToString(", ") { + it.text() + } + description = document.select(".col-12.mt-2").text() + status = parseStatus(document.select(".status-publishing").text()) + thumbnail_url = document.select(".text-center img.img-fluid").attr("src") + } + + override fun chapterListParse(response: Response): List = mutableListOf().apply { + val document = response.asJsoup() + + // One-shot + if (document.select("#chapters").isEmpty()) { + return document.select(oneShotChapterListSelector).map { chapterFromElement(it, oneShotChapterName) } + } + + // Regular list of chapters + val chapterNames = document.select("#chapters h4.text-truncate") + val chapterInfos = document.select("#chapters .chapter-list") + + chapterNames.forEachIndexed { index, _ -> + val scanlator = chapterInfos[index].select("li") + if (getScanlatorPref()) { + scanlator.forEach { add(chapterFromElement(it, chapterNames[index].text())) } + } else { + scanlator.last { add(chapterFromElement(it, chapterNames[index].text())) } + } + } + } + + override fun chapterFromElement(element: Element, chName: String) = SChapter.create().apply { + url = element.select("div.row > .text-right > a").attr("href") + name = chName + scanlator = element.select("div.col-12.text-truncate span").text() + date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { + parseChapterDate(it) + } ?: 0 + } + + override fun imageRequest(page: Page) = GET( + url = page.imageUrl!!, + headers = headers.newBuilder() + .set("Referer", page.url.substringBefore("news/")) + .build(), + ) +} diff --git a/src/es/tumangaonline/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/lectortmo/tumangaonline/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/es/tumangaonline/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/tumangaonline/res/mipmap-hdpi/ic_launcher.png diff --git a/src/es/tumangaonline/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/lectortmo/tumangaonline/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/es/tumangaonline/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/tumangaonline/res/mipmap-mdpi/ic_launcher.png diff --git a/src/es/tumangaonline/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/es/tumangaonline/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/es/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/es/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/es/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/es/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/lectortmo/tumangaonline/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/multisrc/overrides/lectortmo/tumangaonline/src/TuMangaOnline.kt b/multisrc/overrides/lectortmo/tumangaonline/src/TuMangaOnline.kt new file mode 100644 index 000000000..bb2d5eaf9 --- /dev/null +++ b/multisrc/overrides/lectortmo/tumangaonline/src/TuMangaOnline.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.extension.es.tumangaonline + +import eu.kanade.tachiyomi.multisrc.lectortmo.LectorTmo + +class TuMangaOnline : LectorTmo("TuMangaOnline", "https://visortmo.com", "es") { + + override val id = 4146344224513899730 +} diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmo.kt similarity index 81% rename from src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmo.kt index f93ef449d..5329cad94 100644 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnline.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmo.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.es.tumangaonline +package eu.kanade.tachiyomi.multisrc.lectortmo import android.annotation.SuppressLint import android.app.Application @@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup import okhttp3.FormBody -import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request @@ -36,30 +35,31 @@ import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager -class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { - - override val name = "TuMangaOnline" - - override val baseUrl = "https://visortmo.com" - - override val lang = "es" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder { - return Headers.Builder() - .add("Referer", "$baseUrl/") - } - - private val imageCDNUrls = arrayOf( - "https://japanreader.com", - "https://img1.japanreader.com", - ) +abstract class LectorTmo( + override val name: String, + override val baseUrl: String, + override val lang: String, +) : ConfigurableSource, ParsedHttpSource() { private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } + override val supportsLatest = true + + override fun headersBuilder() = super.headersBuilder() + .set("Referer", "$baseUrl/") + + protected open val imageCDNUrls = arrayOf( + "https://img1.followmanga.com", + "https://img1.biggestchef.com", + "https://img1.indalchef.com", + "https://img1.recipesandcook.com", + "https://img1.cyclingte.com", + "https://img1.japanreader.com", + "https://japanreader.com", + ) + private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array, permits: Int, period: Long): OkHttpClient.Builder { hosts.forEach { host -> rateLimitHost(host.toHttpUrl(), permits, period) @@ -115,10 +115,10 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { ) .build() - // Marks erotic content as false and excludes: Ecchi(6), GirlsLove(17), BoysLove(18) and Harem(19) genders - private val getSFWUrlPart = if (getSFWModePref()) "&exclude_genders%5B%5D=6&exclude_genders%5B%5D=17&exclude_genders%5B%5D=18&exclude_genders%5B%5D=19&erotic=false" else "" + // Marks erotic content as false and excludes: Ecchi(6), GirlsLove(17), BoysLove(18), Harem(19), Trap(94) genders + private fun getSFWUrlPart(): String = if (getSFWModePref()) "&exclude_genders%5B%5D=6&exclude_genders%5B%5D=17&exclude_genders%5B%5D=18&exclude_genders%5B%5D=19&exclude_genders%5B%5D=94&erotic=false" else "" - override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title$getSFWUrlPart&_pg=1&page=$page", headers) + override fun popularMangaRequest(page: Int) = GET("$baseUrl/library?order_item=likes_count&order_dir=desc&filter_by=title${getSFWUrlPart()}&_pg=1&page=$page", headers) override fun popularMangaNextPageSelector() = "a[rel='next']" @@ -132,7 +132,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { } } - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title$getSFWUrlPart&_pg=1&page=$page", headers) + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/library?order_item=creation&order_dir=desc&filter_by=title${getSFWUrlPart()}&_pg=1&page=$page", headers) override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() @@ -140,6 +140,28 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return if (query.startsWith(PREFIX_SLUG_SEARCH)) { + val realQuery = query.removePrefix(PREFIX_SLUG_SEARCH) + + client.newCall(searchMangaBySlugRequest(realQuery)) + .asObservableSuccess() + .map { response -> + val details = mangaDetailsParse(response) + details.url = "/$PREFIX_LIBRARY/$realQuery" + MangasPage(listOf(details), false) + } + } else { + client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + } + + private fun searchMangaBySlugRequest(slug: String) = GET("$baseUrl/$PREFIX_LIBRARY/$slug", headers) + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = "$baseUrl/library".toHttpUrl().newBuilder() url.addQueryParameter("title", query) @@ -159,15 +181,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { is Demography -> { url.addQueryParameter("demography", filter.toUriPart()) } - is Status -> { - url.addQueryParameter("status", filter.toUriPart()) - } - is TranslationStatus -> { - url.addQueryParameter("translation_status", filter.toUriPart()) - } - is FilterBy -> { - url.addQueryParameter("filter_by", filter.toUriPart()) - } is SortBy -> { if (filter.state != null) { url.addQueryParameter("order_item", SORTABLES[filter.state!!.index].second) @@ -179,8 +192,6 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { } is ContentTypeList -> { filter.state.forEach { content -> - // If (SFW mode is not enabled) OR (SFW mode is enabled AND filter != erotic) -> Apply filter - // else -> ignore filter if (!getSFWModePref() || (getSFWModePref() && content.id != "erotic")) { when (content.state) { Filter.TriState.STATE_IGNORE -> url.addQueryParameter(content.id, "") @@ -204,8 +215,11 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { return GET(url.build(), headers) } override fun searchMangaSelector() = popularMangaSelector() + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + override fun mangaDetailsParse(document: Document) = SManga.create().apply { title = document.select("h2.element-subtitle").text() document.select("h5.card-title").let { @@ -219,54 +233,63 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { status = parseStatus(document.select("span.book-status").text()) thumbnail_url = document.select(".book-thumbnail").attr("src") } - private fun parseStatus(status: String) = when { + + protected fun parseStatus(status: String) = when { status.contains("Publicándose") -> SManga.ONGOING status.contains("Finalizado") -> SManga.COMPLETED else -> SManga.UNKNOWN } + protected open val oneShotChapterName = "One Shot" + override fun chapterListParse(response: Response): List { val document = response.asJsoup() + // One-shot if (document.select("div.chapters").isEmpty()) { - return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) } + return document.select(oneShotChapterListSelector).map { chapterFromElement(it, oneShotChapterName) } } + // Regular list of chapters val chapters = mutableListOf() - document.select(regularChapterListSelector()).forEach { chapelement -> - val chaptername = chapelement.select("div.col-10.text-truncate").text().replace(" ", " ").trim() - val scanelement = chapelement.select("ul.chapter-list > li") + document.select(regularChapterListSelector).forEach { chapelement -> + val chapterName = chapelement.select("div.col-10.text-truncate").text().replace(" ", " ").trim() + val chapterScanlator = chapelement.select("ul.chapter-list > li") if (getScanlatorPref()) { - scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername)) } + chapterScanlator.forEach { chapters.add(chapterFromElement(it, chapterName)) } } else { - scanelement.first { chapters.add(regularChapterFromElement(it, chaptername)) } + chapterScanlator.first { chapters.add(chapterFromElement(it, chapterName)) } } } return chapters } + + protected open val oneShotChapterListSelector = "div.chapter-list-element > ul.list-group li.list-group-item" + + protected open val regularChapterListSelector = "div.chapters > ul.list-group li.p-0.list-group-item" + override fun chapterListSelector() = throw UnsupportedOperationException() + override fun chapterFromElement(element: Element) = throw UnsupportedOperationException() - private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item" - private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply { - url = element.select("div.row > .text-right > a").attr("href") - name = "One Shot" - 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 - } - private fun regularChapterListSelector() = "div.chapters > ul.list-group li.p-0.list-group-item" - private fun regularChapterFromElement(element: Element, chName: String) = SChapter.create().apply { + + protected open fun chapterFromElement(element: Element, chName: String) = SChapter.create().apply { url = element.select("div.row > .text-right > a").attr("href") name = chName scanlator = element.select("div.col-md-6.text-truncate").text() - 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 parseChapterDate(date: String): Long = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)?.time - ?: 0 + + protected open fun parseChapterDate(date: String): Long { + return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + .parse(date)?.time ?: 0 + } + override fun pageListRequest(chapter: SChapter): Request { return GET(chapter.url, headers) } + override fun pageListParse(document: Document): List = mutableListOf().apply { var doc = redirectToReadPage(document) @@ -302,7 +325,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { } } - private fun redirectToReadPage(document: Document): Document { + private tailrec fun redirectToReadPage(document: Document): Document { val script1 = document.selectFirst("script:containsData(uniqid)") val script2 = document.selectFirst("script:containsData(window.location.replace)") val script3 = document.selectFirst("script:containsData(redirectUrl)") @@ -376,195 +399,8 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { } } - // Note: At this moment (05/04/2023) it's necessary to make the image request with headers to prevent 403. - override fun imageRequest(page: Page): Request { - val imageHeaders = Headers.Builder() - .add("Referer", baseUrl) - .build() - return GET(page.imageUrl!!, imageHeaders) - } - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/$PREFIX_LIBRARY/$id", headers) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$PREFIX_LIBRARY/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private class Types : UriPartFilter( - "Filtrar por tipo", - arrayOf( - Pair("Ver todo", ""), - Pair("Manga", "manga"), - Pair("Manhua", "manhua"), - Pair("Manhwa", "manhwa"), - Pair("Novela", "novel"), - Pair("One shot", "one_shot"), - Pair("Doujinshi", "doujinshi"), - Pair("Oel", "oel"), - ), - ) - - private class Status : UriPartFilter( - "Filtrar por estado de serie", - arrayOf( - Pair("Ver todo", ""), - Pair("Publicándose", "publishing"), - Pair("Finalizado", "ended"), - Pair("Cancelado", "cancelled"), - Pair("Pausado", "on_hold"), - ), - ) - - private class TranslationStatus : UriPartFilter( - "Filtrar por estado de traducción", - arrayOf( - Pair("Ver todo", ""), - Pair("Activo", "publishing"), - Pair("Finalizado", "ended"), - Pair("Abandonado", "cancelled"), - ), - ) - - private class Demography : UriPartFilter( - "Filtrar por demografía", - arrayOf( - Pair("Ver todo", ""), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Josei", "josei"), - Pair("Kodomo", "kodomo"), - ), - ) - - private class FilterBy : UriPartFilter( - "Filtrar por", - arrayOf( - Pair("Título", "title"), - Pair("Autor", "author"), - Pair("Compañia", "company"), - ), - ) - - class SortBy : Filter.Sort( - "Ordenar por", - SORTABLES.map { it.first }.toTypedArray(), - Selection(0, false), - ) - - private class ContentType(name: String, val id: String) : Filter.TriState(name) - - private class ContentTypeList(content: List) : Filter.Group("Filtrar por tipo de contenido", content) - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - private class GenreList(genres: List) : Filter.Group("Filtrar por géneros", genres) - - override fun getFilterList() = FilterList( - Types(), - Filter.Separator(), - Filter.Header("Ignorado sino se filtra por tipo"), - Status(), - Filter.Separator(), - Filter.Header("Ignorado sino se filtra por tipo"), - TranslationStatus(), - Filter.Separator(), - Demography(), - Filter.Separator(), - FilterBy(), - Filter.Separator(), - SortBy(), - Filter.Separator(), - ContentTypeList(getContentTypeList()), - Filter.Separator(), - 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') - // on https://lectortmo.com/library - // Last revision 15/02/2021 - private fun getGenreList() = listOf( - Genre("Acción", "1"), - Genre("Aventura", "2"), - Genre("Comedia", "3"), - Genre("Drama", "4"), - Genre("Recuentos de la vida", "5"), - Genre("Ecchi", "6"), - Genre("Fantasia", "7"), - Genre("Magia", "8"), - Genre("Sobrenatural", "9"), - Genre("Horror", "10"), - Genre("Misterio", "11"), - Genre("Psicológico", "12"), - Genre("Romance", "13"), - Genre("Ciencia Ficción", "14"), - Genre("Thriller", "15"), - Genre("Deporte", "16"), - Genre("Girls Love", "17"), - Genre("Boys Love", "18"), - Genre("Harem", "19"), - Genre("Mecha", "20"), - Genre("Supervivencia", "21"), - Genre("Reencarnación", "22"), - Genre("Gore", "23"), - Genre("Apocalíptico", "24"), - Genre("Tragedia", "25"), - Genre("Vida Escolar", "26"), - Genre("Historia", "27"), - Genre("Militar", "28"), - Genre("Policiaco", "29"), - Genre("Crimen", "30"), - Genre("Superpoderes", "31"), - Genre("Vampiros", "32"), - Genre("Artes Marciales", "33"), - Genre("Samurái", "34"), - Genre("Género Bender", "35"), - Genre("Realidad Virtual", "36"), - Genre("Ciberpunk", "37"), - Genre("Musica", "38"), - Genre("Parodia", "39"), - Genre("Animación", "40"), - Genre("Demonios", "41"), - Genre("Familia", "42"), - Genre("Extranjero", "43"), - Genre("Niños", "44"), - Genre("Realidad", "45"), - Genre("Telenovela", "46"), - Genre("Guerra", "47"), - Genre("Oeste", "48"), - ) - - private fun getContentTypeList() = listOf( - ContentType("Webcomic", "webcomic"), - ContentType("Yonkoma", "yonkoma"), - ContentType("Amateur", "amateur"), - ContentType("Erótico", "erotic"), - ) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { val sfwModePref = androidx.preference.CheckBoxPreference(screen.context).apply { key = SFW_MODE_PREF @@ -635,9 +471,125 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { screen.addPreference(imgCDNRateLimitPreference) } - private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE) + override fun getFilterList() = FilterList( + FilterBy(), + Filter.Separator(), + SortBy(), + Filter.Separator(), + Types(), + Demography(), + ContentTypeList(getContentTypeList()), + Filter.Separator(), + GenreList(getGenreList()), + ) - private fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE) + private class FilterBy : UriPartFilter( + "Buscar por", + arrayOf( + Pair("Título", "title"), + Pair("Autor", "author"), + Pair("Compañia", "company"), + ), + ) + + class SortBy : Filter.Sort( + "Ordenar por", + SORTABLES.map { it.first }.toTypedArray(), + Selection(0, false), + ) + + private class Types : UriPartFilter( + "Filtrar por tipo", + arrayOf( + Pair("Ver todo", ""), + Pair("Manga", "manga"), + Pair("Manhua", "manhua"), + Pair("Manhwa", "manhwa"), + Pair("Novela", "novel"), + Pair("One shot", "one_shot"), + Pair("Doujinshi", "doujinshi"), + Pair("Oel", "oel"), + ), + ) + + private class Demography : UriPartFilter( + "Filtrar por demografía", + arrayOf( + Pair("Ver todo", ""), + Pair("Seinen", "seinen"), + Pair("Shoujo", "shoujo"), + Pair("Shounen", "shounen"), + Pair("Josei", "josei"), + Pair("Kodomo", "kodomo"), + ), + ) + + private fun getContentTypeList() = listOf( + ContentType("Webcomic", "webcomic"), + ContentType("Yonkoma", "yonkoma"), + ContentType("Amateur", "amateur"), + ContentType("Erótico", "erotic"), + ) + + // Array.from(document.querySelectorAll('#books-genders .col-auto .custom-control')) + // .map(a => `Genre("${a.querySelector('label').innerText}", "${a.querySelector('input').value}")`).join(',\n') + // on ${baseUrl}/library + // Last revision 02/04/2024 (mm/dd/yyyy) + private fun getGenreList() = listOf( + Genre("Acción", "1"), + Genre("Aventura", "2"), + Genre("Comedia", "3"), + Genre("Drama", "4"), + Genre("Recuentos de la vida", "5"), + Genre("Ecchi", "6"), + Genre("Fantasia", "7"), + Genre("Magia", "8"), + Genre("Sobrenatural", "9"), + Genre("Horror", "10"), + Genre("Misterio", "11"), + Genre("Psicológico", "12"), + Genre("Romance", "13"), + Genre("Ciencia Ficción", "14"), + Genre("Thriller", "15"), + Genre("Deporte", "16"), + Genre("Girls Love", "17"), + Genre("Boys Love", "18"), + Genre("Harem", "19"), + Genre("Mecha", "20"), + Genre("Supervivencia", "21"), + Genre("Reencarnación", "22"), + Genre("Gore", "23"), + Genre("Apocalíptico", "24"), + Genre("Tragedia", "25"), + Genre("Vida Escolar", "26"), + Genre("Historia", "27"), + Genre("Militar", "28"), + Genre("Policiaco", "29"), + Genre("Crimen", "30"), + Genre("Superpoderes", "31"), + Genre("Vampiros", "32"), + Genre("Artes Marciales", "33"), + Genre("Samurái", "34"), + Genre("Género Bender", "35"), + Genre("Realidad Virtual", "36"), + Genre("Ciberpunk", "37"), + Genre("Musica", "38"), + Genre("Parodia", "39"), + Genre("Animación", "40"), + Genre("Demonios", "41"), + Genre("Familia", "42"), + Genre("Extranjero", "43"), + Genre("Niños", "44"), + Genre("Realidad", "45"), + Genre("Telenovela", "46"), + Genre("Guerra", "47"), + Genre("Oeste", "48"), + Genre("Trap", "94"), + ) + + protected fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE) + + protected fun getSFWModePref(): Boolean = preferences.getBoolean(SFW_MODE_PREF, SFW_MODE_PREF_DEFAULT_VALUE) companion object { private const val SCANLATOR_PREF = "scanlatorPref" @@ -647,7 +599,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { private const val SFW_MODE_PREF = "SFWModePref" private const val SFW_MODE_PREF_TITLE = "Ocultar contenido NSFW" - private const val SFW_MODE_PREF_SUMMARY = "Ocultar el contenido erótico (puede que aún activandolo se sigan mostrando portadas o series NSFW). Ten en cuenta que al activarlo se ignoran filtros al explorar y buscar.\nLos filtros ignorados son: Filtrar por tipo de contenido (Erotico) y el Filtrar por generos: Ecchi, Boys Love, Girls Love y Harem." + private const val SFW_MODE_PREF_SUMMARY = "Ocultar el contenido erótico (puede que aún activandolo se sigan mostrando portadas o series NSFW). Ten en cuenta que al activarlo se ignoran filtros al explorar y buscar.\nLos filtros ignorados son: Filtrar por tipo de contenido (Erotico) y el Filtrar por generos: Ecchi, Boys Love, Girls Love, Harem y Trap." private const val SFW_MODE_PREF_DEFAULT_VALUE = false private val SFW_MODE_PREF_EXCLUDE_GENDERS = listOf("6", "17", "18", "19") @@ -672,7 +624,7 @@ class TuMangaOnline : ConfigurableSource, ParsedHttpSource() { private val ENTRIES_ARRAY = listOf(1, 2, 3, 5, 6, 7, 8, 9, 10, 15, 20, 30, 40, 50, 100).map { i -> i.toString() }.toTypedArray() const val PREFIX_LIBRARY = "library" - const val PREFIX_ID_SEARCH = "id:" + const val PREFIX_SLUG_SEARCH = "slug:" private val SORTABLES = listOf( Pair("Me gusta", "likes_count"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoFilters.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoFilters.kt new file mode 100644 index 000000000..17f1d1b3f --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoFilters.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.multisrc.lectortmo + +import eu.kanade.tachiyomi.source.model.Filter + +class ContentType(name: String, val id: String) : Filter.TriState(name) + +class ContentTypeList(content: List) : Filter.Group("Filtrar por tipo de contenido", content) + +class Genre(name: String, val id: String) : Filter.TriState(name) + +class GenreList(genres: List) : Filter.Group("Filtrar por géneros", genres) + +open class UriPartFilter(displayName: String, val vals: Array>) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + fun toUriPart() = vals[state].second +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoGenerator.kt new file mode 100644 index 000000000..7fcee7e32 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoGenerator.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.multisrc.lectortmo + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class LectorTmoGenerator : ThemeSourceGenerator { + + override val themePkg = "lectortmo" + + override val themeClass = "LectorTmo" + + override val baseVersionCode: Int = 1 + + override val sources = listOf( + SingleLang("LectorManga", "https://lectormanga.com", "es", isNsfw = true, overrideVersionCode = 34), + SingleLang("TuMangaOnline", "https://visortmo.com", "es", isNsfw = true, overrideVersionCode = 49), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + LectorTmoGenerator().createAll() + } + } +} diff --git a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoUrlActivity.kt similarity index 65% rename from src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoUrlActivity.kt index 05607386c..489afad29 100644 --- a/src/es/tumangaonline/src/eu/kanade/tachiyomi/extension/es/tumangaonline/TuMangaOnlineUrlActivity.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/lectortmo/LectorTmoUrlActivity.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.es.tumangaonline +package eu.kanade.tachiyomi.multisrc.lectortmo import android.app.Activity import android.content.ActivityNotFoundException @@ -7,11 +7,7 @@ import android.os.Bundle import android.util.Log import kotlin.system.exitProcess -/** - * Springboard that accepts https://visortmo.com/library/:type/:id/:slug intents and redirects them to - * the main Tachiyomi process. - */ -class TuMangaOnlineUrlActivity : Activity() { +class LectorTmoUrlActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val pathSegments = intent?.data?.pathSegments @@ -23,17 +19,17 @@ class TuMangaOnlineUrlActivity : Activity() { val mainIntent = Intent().apply { action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${TuMangaOnline.PREFIX_ID_SEARCH}$type/$id/$slug") + putExtra("query", "${LectorTmo.PREFIX_SLUG_SEARCH}$type/$id/$slug") putExtra("filter", packageName) } try { startActivity(mainIntent) } catch (e: ActivityNotFoundException) { - Log.e("TMOUrlActivity", e.toString()) + Log.e("LectorTmoUrlActivity", e.toString()) } } else { - Log.e("TMOUrlActivity", "could not parse uri from intent $intent") + Log.e("LectorTmoUrlActivity", "could not parse uri from intent $intent") } finish() diff --git a/src/es/lectormanga/AndroidManifest.xml b/src/es/lectormanga/AndroidManifest.xml deleted file mode 100644 index 9ebcab855..000000000 --- a/src/es/lectormanga/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/es/lectormanga/build.gradle b/src/es/lectormanga/build.gradle deleted file mode 100644 index 4b7384b8a..000000000 --- a/src/es/lectormanga/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - extName = 'LectorManga' - extClass = '.LectorManga' - extVersionCode = 34 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt deleted file mode 100644 index fe48ddfdc..000000000 --- a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorManga.kt +++ /dev/null @@ -1,660 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.lectormanga - -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.network.interceptor.rateLimitHost -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -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.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.security.SecureRandom -import java.security.cert.X509Certificate -import java.text.SimpleDateFormat -import java.util.Locale -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.X509TrustManager - -class LectorManga : ConfigurableSource, ParsedHttpSource() { - - override val name = "LectorManga" - - override val baseUrl = "https://lectormanga.com" - - override val lang = "es" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder { - return Headers.Builder() - .add("Referer", "$baseUrl/") - } - - private val imageCDNUrls = arrayOf( - "https://img1.followmanga.com", - "https://img1.biggestchef.com", - "https://img1.indalchef.com", - "https://img1.recipesandcook.com", - "https://img1.cyclingte.com", - "https://img1.japanreader.com", - "https://japanreader.com", - ) - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array, permits: Int, period: Long): OkHttpClient.Builder { - hosts.forEach { host -> - rateLimitHost(host.toHttpUrl(), permits, period) - } - return this - } - - private fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder { - val naiveTrustManager = @SuppressLint("CustomX509TrustManager") - object : X509TrustManager { - override fun getAcceptedIssuers(): Array = emptyArray() - override fun checkClientTrusted(certs: Array, authType: String) = Unit - override fun checkServerTrusted(certs: Array, authType: String) = Unit - } - - val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply { - val trustAllCerts = arrayOf(naiveTrustManager) - init(null, trustAllCerts, SecureRandom()) - }.socketFactory - - sslSocketFactory(insecureSocketFactory, naiveTrustManager) - hostnameVerifier { _, _ -> true } - return this - } - - private val ignoreSslClient = network.client.newBuilder() - .ignoreAllSSLErrors() - .followRedirects(false) - .rateLimit( - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .build() - - override val client: OkHttpClient = network.client.newBuilder() - .addInterceptor { chain -> - val request = chain.request() - val url = request.url - if (url.host.contains("japanreader.com")) { - return@addInterceptor ignoreSslClient.newCall(request).execute() - } - chain.proceed(request) - } - .rateLimitHost( - baseUrl.toHttpUrl(), - preferences.getString(WEB_RATELIMIT_PREF, WEB_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .rateLimitImageCDNs( - imageCDNUrls, - preferences.getString(IMAGE_CDN_RATELIMIT_PREF, IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE)!!.toInt(), - 60, - ) - .build() - - 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() = "a[rel='next']" - - override fun popularMangaSelector() = ".col-6 .card" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - title = element.select("a").text() - thumbnail_url = element.select("img").attr("src") - } - - override fun 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) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/library".toHttpUrl().newBuilder() - - url.addQueryParameter("title", query) - url.addQueryParameter("page", page.toString()) - - filters.forEach { filter -> - when (filter) { - is Types -> { - url.addQueryParameter("type", filter.toUriPart()) - } - is Demography -> { - url.addQueryParameter("demography", filter.toUriPart()) - } - is FilterBy -> { - url.addQueryParameter("filter_by", filter.toUriPart()) - } - is SortBy -> { - 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 WebcomicFilter -> { - url.addQueryParameter( - "webcomic", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - }, - ) - } - is FourKomaFilter -> { - url.addQueryParameter( - "yonkoma", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - }, - ) - } - is AmateurFilter -> { - url.addQueryParameter( - "amateur", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - }, - ) - } - is EroticFilter -> { - url.addQueryParameter( - "erotic", - when (filter.state) { - Filter.TriState.STATE_INCLUDE -> "true" - Filter.TriState.STATE_EXCLUDE -> "false" - else -> "" - }, - ) - } - is GenreList -> { - filter.state - .filter { genre -> genre.state } - .forEach { genre -> url.addQueryParameter("genders[]", genre.id) } - } - else -> {} - } - } - - return GET(url.build(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select("h1:has(small)").text() - genre = document.select("a.py-2").joinToString(", ") { - it.text() - } - description = document.select(".col-12.mt-2").text() - status = parseStatus(document.select(".status-publishing").text()) - thumbnail_url = document.select(".text-center img.img-fluid").attr("src") - } - - private fun parseStatus(status: String) = when { - status.contains("Publicándose") -> SManga.ONGOING - status.contains("Finalizado") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List = mutableListOf().apply { - val document = response.asJsoup() - - // One-shot - if (document.select("#chapters").isEmpty()) { - return document.select(oneShotChapterListSelector()).map { oneShotChapterFromElement(it) } - } - - // Regular list of chapters - val chapterNames = document.select("#chapters h4.text-truncate") - val chapterInfos = document.select("#chapters .chapter-list") - - chapterNames.forEachIndexed { index, _ -> - val scanlator = chapterInfos[index].select("li") - if (getScanlatorPref()) { - scanlator.forEach { add(regularChapterFromElement(chapterNames[index].text(), it)) } - } else { - scanlator.last { add(regularChapterFromElement(chapterNames[index].text(), it)) } - } - } - } - - override fun chapterListSelector() = throw UnsupportedOperationException() - - override fun chapterFromElement(element: Element) = throw UnsupportedOperationException() - - private fun oneShotChapterListSelector() = "div.chapter-list-element > ul.list-group li.list-group-item" - - private fun oneShotChapterFromElement(element: Element) = SChapter.create().apply { - url = element.select("div.row > .text-right > a").attr("href") - name = "One Shot" - scanlator = element.select("div.col-12.col-sm-12.col-md-4.text-truncate span").text() - date_upload = element.select("span.badge.badge-primary.p-2").first()?.text()?.let { parseChapterDate(it) } - ?: 0 - } - - private fun regularChapterFromElement(chapterName: String, info: Element) = SChapter.create().apply { - url = info.select("div.row > .text-right > a").attr("href") - name = chapterName - scanlator = info.select("div.col-12.col-sm-12.col-md-4.text-truncate span").text() - date_upload = info.select("span.badge.badge-primary.p-2").first()?.text()?.let { - parseChapterDate(it) - } ?: 0 - } - - private fun parseChapterDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - .parse(date)?.time ?: 0 - } - - override fun pageListRequest(chapter: SChapter): Request { - return GET(chapter.url, headers) - } - - override fun pageListParse(document: Document): List = mutableListOf().apply { - var doc = redirectToReadPage(document) - val currentUrl = doc.location() - - val newUrl = if (!currentUrl.contains("cascade")) { - currentUrl.substringBefore("paginated") + "cascade" - } else { - currentUrl - } - - if (currentUrl != newUrl) { - val redirectHeaders = super.headersBuilder() - .set("Referer", doc.location()) - .build() - doc = client.newCall(GET(newUrl, redirectHeaders)).execute().asJsoup() - } - - doc.select("div.viewer-container img:not(noscript img)").forEach { - add( - Page( - size, - doc.location(), - it.let { - if (it.hasAttr("data-src")) { - it.attr("abs:data-src") - } else { - it.attr("abs:src") - } - }, - ), - ) - } - } - - private fun redirectToReadPage(document: Document): Document { - val script1 = document.selectFirst("script:containsData(uniqid)") - val script2 = document.selectFirst("script:containsData(window.location.replace)") - val script3 = document.selectFirst("script:containsData(redirectUrl)") - val script4 = document.selectFirst("input#redir") - val script5 = document.selectFirst("script:containsData(window.opener):containsData(location.replace)") - - val redirectHeaders = super.headersBuilder() - .set("Referer", document.location()) - .build() - - if (script1 != null) { - val data = script1.data() - val regexParams = """\{uniqid:'(.+)',cascade:(.+)\}""".toRegex() - val regexAction = """form\.action\s?=\s?'(.+)'""".toRegex() - val params = regexParams.find(data) - val action = regexAction.find(data)?.groupValues?.get(1)?.unescapeUrl() - - if (params != null && action != null) { - val formBody = FormBody.Builder() - .add("uniqid", params.groupValues[1]) - .add("cascade", params.groupValues[2]) - .build() - return redirectToReadPage(client.newCall(POST(action, redirectHeaders, formBody)).execute().asJsoup()) - } - } - - if (script2 != null) { - val data = script2.data() - val regexRedirect = """window\.location\.replace\(['"](.+)['"]\)""".toRegex() - val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() - - if (url != null) { - return redirectToReadPage(client.newCall(GET(url, redirectHeaders)).execute().asJsoup()) - } - } - - if (script3 != null) { - val data = script3.data() - val regexRedirect = """redirectUrl\s?=\s?'(.+)'""".toRegex() - val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() - - if (url != null) { - return redirectToReadPage(client.newCall(GET(url, redirectHeaders)).execute().asJsoup()) - } - } - - if (script4 != null) { - val url = script4.attr("value").unescapeUrl() - - return redirectToReadPage(client.newCall(GET(url, redirectHeaders)).execute().asJsoup()) - } - - if (script5 != null) { - val data = script5.data() - val regexRedirect = """;[^.]location\.replace\(['"](.+)['"]\)""".toRegex() - val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl() - - if (url != null) { - return redirectToReadPage(client.newCall(GET(url, redirectHeaders)).execute().asJsoup()) - } - } - - return document - } - - private fun String.unescapeUrl(): String { - return if (this.startsWith("http:\\/\\/") || this.startsWith("https:\\/\\/")) { - this.replace("\\/", "/") - } else { - this - } - } - - override fun imageRequest(page: Page) = GET( - url = page.imageUrl!!, - headers = headers.newBuilder() - .removeAll("Referer") - .add("Referer", page.url.substringBefore("news/")) - .build(), - ) - - override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() - - private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/$MANGA_URL_CHUNK/$id", headers) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(PREFIX_ID_SEARCH)) { - val realQuery = query.removePrefix(PREFIX_ID_SEARCH) - - client.newCall(searchMangaByIdRequest(realQuery)) - .asObservableSuccess() - .map { response -> - val details = mangaDetailsParse(response) - details.url = "/$MANGA_URL_CHUNK/$realQuery" - MangasPage(listOf(details), false) - } - } else { - client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - } - - private class Types : UriPartFilter( - "Filtrar por tipo", - arrayOf( - Pair("Ver todos", ""), - Pair("Manga", "manga"), - Pair("Manhua", "manhua"), - Pair("Manhwa", "manhwa"), - Pair("Novela", "novel"), - Pair("One shot", "one_shot"), - Pair("Doujinshi", "doujinshi"), - Pair("Oel", "oel"), - ), - ) - - private class Demography : UriPartFilter( - "Filtrar por demografía", - arrayOf( - Pair("Ver todas", ""), - Pair("Seinen", "seinen"), - Pair("Shoujo", "shoujo"), - Pair("Shounen", "shounen"), - Pair("Josei", "josei"), - Pair("Kodomo", "kodomo"), - ), - ) - - private class FilterBy : UriPartFilter( - "Campo de orden", - arrayOf( - Pair("Título", "title"), - Pair("Autor", "author"), - Pair("Compañia", "company"), - ), - ) - - class SortBy : Filter.Sort( - "Ordenar por", - SORTABLES.map { it.first }.toTypedArray(), - Selection(0, false), - ) - - private class WebcomicFilter : Filter.TriState("Webcomic") - - private class FourKomaFilter : Filter.TriState("Yonkoma") - - private class AmateurFilter : Filter.TriState("Amateur") - - private class EroticFilter : Filter.TriState("Erótico") - - private class Genre(name: String, val id: String) : Filter.CheckBox(name) - - private class GenreList(genres: List) : Filter.Group("Filtrar por géneros", genres) - - override fun getFilterList() = FilterList( - Types(), - Demography(), - Filter.Separator(), - FilterBy(), - SortBy(), - Filter.Separator(), - WebcomicFilter(), - FourKomaFilter(), - AmateurFilter(), - EroticFilter(), - GenreList(getGenreList()), - ) - - // 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 - // Last revision 30/08/2021 - private fun getGenreList() = listOf( - Genre("Acción", "1"), - Genre("Aventura", "2"), - Genre("Comedia", "3"), - Genre("Drama", "4"), - Genre("Recuentos de la vida", "5"), - Genre("Ecchi", "6"), - Genre("Fantasia", "7"), - Genre("Magia", "8"), - Genre("Sobrenatural", "9"), - Genre("Horror", "10"), - Genre("Misterio", "11"), - Genre("Psicológico", "12"), - Genre("Romance", "13"), - Genre("Ciencia Ficción", "14"), - Genre("Thriller", "15"), - Genre("Deporte", "16"), - Genre("Girls Love", "17"), - Genre("Boys Love", "18"), - Genre("Harem", "19"), - Genre("Mecha", "20"), - Genre("Supervivencia", "21"), - Genre("Reencarnación", "22"), - Genre("Gore", "23"), - Genre("Apocalíptico", "24"), - Genre("Tragedia", "25"), - Genre("Vida Escolar", "26"), - Genre("Historia", "27"), - Genre("Militar", "28"), - Genre("Policiaco", "29"), - Genre("Crimen", "30"), - Genre("Superpoderes", "31"), - Genre("Vampiros", "32"), - Genre("Artes Marciales", "33"), - Genre("Samurái", "34"), - Genre("Género Bender", "35"), - Genre("Realidad Virtual", "36"), - Genre("Ciberpunk", "37"), - Genre("Musica", "38"), - Genre("Parodia", "39"), - Genre("Animación", "40"), - Genre("Demonios", "41"), - Genre("Familia", "42"), - Genre("Extranjero", "43"), - Genre("Niños", "44"), - Genre("Realidad", "45"), - Genre("Telenovela", "46"), - Genre("Guerra", "47"), - Genre("Oeste", "48"), - ) - - private open class UriPartFilter(displayName: String, val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val scanlatorPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = SCANLATOR_PREF - title = SCANLATOR_PREF_TITLE - summary = SCANLATOR_PREF_SUMMARY - setDefaultValue(SCANLATOR_PREF_DEFAULT_VALUE) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SCANLATOR_PREF, checkValue).commit() - } - } - - // Rate limit - val apiRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = WEB_RATELIMIT_PREF - title = WEB_RATELIMIT_PREF_TITLE - summary = WEB_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(WEB_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(WEB_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - val imgCDNRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { - key = IMAGE_CDN_RATELIMIT_PREF - title = IMAGE_CDN_RATELIMIT_PREF_TITLE - summary = IMAGE_CDN_RATELIMIT_PREF_SUMMARY - entries = ENTRIES_ARRAY - entryValues = ENTRIES_ARRAY - - setDefaultValue(IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE) - setOnPreferenceChangeListener { _, newValue -> - try { - val setting = preferences.edit().putString(IMAGE_CDN_RATELIMIT_PREF, newValue as String).commit() - setting - } catch (e: Exception) { - e.printStackTrace() - false - } - } - } - - screen.addPreference(scanlatorPref) - screen.addPreference(apiRateLimitPreference) - screen.addPreference(imgCDNRateLimitPreference) - } - - private fun getScanlatorPref(): Boolean = preferences.getBoolean(SCANLATOR_PREF, SCANLATOR_PREF_DEFAULT_VALUE) - - companion object { - private const val SCANLATOR_PREF = "scanlatorPref" - private const val SCANLATOR_PREF_TITLE = "Mostrar todos los scanlator" - private const val SCANLATOR_PREF_SUMMARY = "Se mostraran capítulos repetidos pero con diferentes Scanlators" - private const val SCANLATOR_PREF_DEFAULT_VALUE = true - - private const val WEB_RATELIMIT_PREF = "webRatelimitPreference" - - // Ratelimit permits per second for main website - private const val WEB_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para el sitio web" - - // This value affects network request amount to TMO url. Lower this value may reduce the chance to get HTTP 429 error, but loading speed will be slower too. App restart required. \nCurrent value: %s - private const val WEB_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red a la URL de TMO. Reducir este valor puede disminuir la posibilidad de obtener un error HTTP 429, pero la velocidad de descarga será más lenta. Se requiere reiniciar la app. \nValor actual: %s" - private const val WEB_RATELIMIT_PREF_DEFAULT_VALUE = "8" - - private const val IMAGE_CDN_RATELIMIT_PREF = "imgCDNRatelimitPreference" - - // Ratelimit permits per second for image CDN - private const val IMAGE_CDN_RATELIMIT_PREF_TITLE = "Ratelimit por minuto para descarga de imágenes" - - // This value affects network request amount for loading image. Lower this value may reduce the chance to get error when loading image, but loading speed will be slower too. App restart required. \nCurrent value: %s - private const val IMAGE_CDN_RATELIMIT_PREF_SUMMARY = "Este valor afecta la cantidad de solicitudes de red para descargar imágenes. Reducir este valor puede disminuir errores al cargar imagenes, pero la velocidad de descarga será más lenta. Se requiere reiniciar la app. \nValor actual: %s" - private const val IMAGE_CDN_RATELIMIT_PREF_DEFAULT_VALUE = "50" - - private val ENTRIES_ARRAY = arrayOf("1", "2", "3", "5", "6", "7", "8", "9", "10", "15", "20", "30", "40", "50", "100") - - const val PREFIX_ID_SEARCH = "id:" - const val MANGA_URL_CHUNK = "gotobook" - - 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"), - ) - } -} diff --git a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt b/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt deleted file mode 100644 index 3cad95e69..000000000 --- a/src/es/lectormanga/src/eu/kanade/tachiyomi/extension/es/lectormanga/LectorMangaUrlActivity.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.es.lectormanga - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -/** - * Springboard that accepts https://lectormanga.com/gotobook/:id intents and redirects them to - * the main Tachiyomi process. - */ -class LectorMangaUrlActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - - if (pathSegments != null && pathSegments.size > 1) { - val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${LectorManga.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("LectorMangaUrlActivity", e.toString()) - } - } else { - Log.e("LectorMangaUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/es/tumangaonline/build.gradle b/src/es/tumangaonline/build.gradle deleted file mode 100644 index e9becf73d..000000000 --- a/src/es/tumangaonline/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -ext { - extName = 'TuMangaOnline' - extClass = '.TuMangaOnline' - extVersionCode = 49 - isNsfw = true -} - -apply from: "$rootDir/common.gradle"