Move TMO and LectorManga to multisrc (#1002)
* Theme-lib when * Lint * Order generator * Apply no-nsfw pref on request instead of init
@ -3,7 +3,7 @@
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".es.tumangaonline.TuMangaOnlineUrlActivity"
|
||||
android:name="eu.kanade.tachiyomi.multisrc.lectortmo.LectorTmoUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
@ -12,11 +12,10 @@
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="visortmo.com"
|
||||
android:host="${SOURCEHOST}"
|
||||
android:pathPattern="/library/..*/..*/..*"
|
||||
android:scheme="https" />
|
||||
android:scheme="${SOURCESCHEME}" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
72
multisrc/overrides/lectortmo/lectormanga/src/LectorManga.kt
Normal file
@ -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<SChapter> = mutableListOf<SChapter>().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(),
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -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
|
||||
}
|
@ -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<Application>().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<String>, 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<MangasPage> {
|
||||
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<SChapter> {
|
||||
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<SChapter>()
|
||||
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<Page> = mutableListOf<Page>().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<MangasPage> {
|
||||
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<ContentType>) : Filter.Group<ContentType>("Filtrar por tipo de contenido", content)
|
||||
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("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<Pair<String, String>>) :
|
||||
Filter.Select<String>(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"),
|
@ -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<ContentType>) : Filter.Group<ContentType>("Filtrar por tipo de contenido", content)
|
||||
|
||||
class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Filtrar por géneros", genres)
|
||||
|
||||
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
@ -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<String>) {
|
||||
LectorTmoGenerator().createAll()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".es.lectormanga.LectorMangaUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="lectormanga.com"
|
||||
android:pathPattern="/gotobook/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
@ -1,8 +0,0 @@
|
||||
ext {
|
||||
extName = 'LectorManga'
|
||||
extClass = '.LectorManga'
|
||||
extVersionCode = 34
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
@ -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<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.rateLimitImageCDNs(hosts: Array<String>, 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<X509Certificate> = emptyArray()
|
||||
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||
}
|
||||
|
||||
val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
|
||||
val trustAllCerts = arrayOf<TrustManager>(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<SChapter> = mutableListOf<SChapter>().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<Page> = mutableListOf<Page>().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<MangasPage> {
|
||||
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<Genre>) : Filter.Group<Genre>("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<Pair<String, String>>) :
|
||||
Filter.Select<String>(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"),
|
||||
)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
ext {
|
||||
extName = 'TuMangaOnline'
|
||||
extClass = '.TuMangaOnline'
|
||||
extVersionCode = 49
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|