Migrate MMK to MangaThemesia. (#14616)

This commit is contained in:
Alessandro Jean 2022-12-20 12:18:48 -03:00 committed by GitHub
parent 7737c17251
commit 00099ca121
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 25 additions and 299 deletions

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,24 @@
package eu.kanade.tachiyomi.extension.pt.mundomangakun
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MundoMangaKun : MangaThemesia(
"Mundo Mangá-Kun",
"https://mundomangakun.com.br",
"pt-BR",
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR"))
) {
// Changed their theme from a custom one to MangaThemesia.
// The URLs are incompatible between the versions.
override val versionId = 2
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()
}

View File

@ -78,6 +78,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
SingleLang("MELOKOMIK", "https://melokomik.xyz", "id"),
SingleLang("Mihentai", "https://mihentai.com", "all", isNsfw = true, overrideVersionCode = 2),
SingleLang("Mode Scanlator", "https://modescanlator.com", "pt-BR", overrideVersionCode = 8),
SingleLang("Mundo Mangá-Kun", "https://mundomangakun.com.br", "pt-BR", className = "MundoMangaKun", isNsfw = true),
SingleLang("Nekomik", "https://nekomik.com", "id"),
SingleLang("Ngomik", "https://ngomik.net", "id", overrideVersionCode = 2),
SingleLang("NIGHT SCANS", "https://nightscans.org", "en", isNsfw = true, className = "NightScans"),

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,13 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Mundo Mangá-Kun'
pkgNameSuffix = 'pt.mundomangakun'
extClass = '.MundoMangaKun'
extVersionCode = 9
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

@ -1,284 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.mundomangakun
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
class MundoMangaKun : ParsedHttpSource() {
override val name = "Mundo Mangá-Kun"
override val baseUrl = "https://mundomangakun.com.br"
override val lang = "pt-BR"
override val supportsLatest = false
override val client: OkHttpClient = network.client.newBuilder()
.rateLimit(1, 3, TimeUnit.SECONDS)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", USER_AGENT)
.add("Origin", baseUrl)
.add("Referer", baseUrl)
private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int): Request {
val refererPath = if (page <= 2) "" else "/leitor-online/${page - 1}"
val newHeaders = headersBuilder()
.set("Referer", baseUrl + refererPath)
.build()
val pageStr = if (page != 1) "/page/$page" else ""
return GET("$baseUrl/leitor-online$pageStr", newHeaders)
}
override fun popularMangaSelector(): String = "div.leitor_online_container article.manga_item"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("h2.titulo_manga_item").text()
thumbnail_url = element.select("div.container_imagem").attr("style")
.substringAfter("url(")
.substringBefore(");")
setUrlWithoutDomain(element.select("h2.titulo_manga_item a").attr("href"))
}
override fun popularMangaNextPageSelector() = "div.paginacao a.next.page-numbers"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val refererPage = if (page <= 2) "" else "page/${page - 1}"
val newHeaders = headersBuilder()
.set("Referer", "$baseUrl/leitor-online/$refererPage")
.build()
val pagePath = if (page != 1) "page/$page/" else ""
val url = "$baseUrl/leitor-online/$pagePath".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("leitor_titulo_projeto", query)
filters.forEach { filter ->
when (filter) {
is GenreFilter -> {
url.addQueryParameter("leitor_genero_projeto", filter.selected.value)
}
is StatusFilter -> {
url.addQueryParameter("leitor_status_projeto", filter.selected.value)
}
is SortFilter -> {
val order = if (filter.state!!.ascending) "ASC" else "DESC"
url.addQueryParameter("leitor_ordem_projeto", order)
}
}
}
return GET(url.toString(), newHeaders)
}
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
val infoElement = document.select("div.main_container_projeto div.row").first()
val colInfo = infoElement.select("div.col-sm-7").first()
val colImg = infoElement.select("div.col-sm-5").first()
val tableInfo = colInfo.select("table.tabela_info_projeto").first()
title = colInfo.select("h1.titulo_projeto").text()
author = tableInfo.select("td:contains(Roteiro) + td").text()
artist = tableInfo.select("td:contains(Arte) + td").text()
genre = colImg.select("div.generos a.link_genero").joinToString { it.text() }
status = tableInfo.select("td:contains(Status no Scan) + td").text().toStatus()
description = colInfo.select("h2:contains(Sinopse) + div.conteudo_projeto").text()
thumbnail_url = infoElement.select("div.imagens_projeto_container img").first().attr("src")
}
override fun chapterListParse(response: Response): List<SChapter> {
return super.chapterListParse(response).reversed()
}
override fun chapterListSelector() = "div.capitulos_leitor_online a.link_capitulo"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.text()
scanlator = this@MundoMangaKun.name
val link = element.attr("onclick")
.substringAfter("this,")
.substringBeforeLast(")")
.replace("'", "\"")
.let { json.parseToJsonElement(it) }
.jsonArray
.first { it.jsonObject["tipo"]!!.jsonPrimitive.content == "LEITOR" }
setUrlWithoutDomain(link.jsonObject["link"]!!.jsonPrimitive.content)
}
override fun pageListParse(document: Document): List<Page> {
val pageList = document.select("script:containsData(var paginas)").first().data()
.substringAfter("var paginas = ")
.substringBefore("];")
.let { json.parseToJsonElement("$it]") }
.jsonArray
.mapIndexed { i, page -> Page(i, document.location(), page.jsonPrimitive.content) }
// Check if the pages have exceeded the view limit of Google Drive.
val firstPage = pageList[0]
val hasExceededViewLimit = runCatching {
val firstPageRequest = imageRequest(firstPage)
client.newCall(firstPageRequest).execute().use {
val isHtml = it.headers["Content-Type"]!!.contains("text/html")
GoogleDriveResponse(!isHtml && it.isSuccessful, it.code)
}
}
val defaultResponse = GoogleDriveResponse(false, GD_BACKEND_ERROR)
val googleDriveResponse = hasExceededViewLimit.getOrDefault(defaultResponse)
if (!googleDriveResponse.isValid) {
throw Exception(googleDriveResponse.errorMessage)
}
return pageList
}
override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request {
val newHeaders = headersBuilder()
.set("Referer", page.url)
.build()
return GET(page.imageUrl!!, newHeaders)
}
override fun getFilterList(): FilterList = FilterList(
GenreFilter(getGenreList()),
StatusFilter(getStatusList()),
SortFilter()
)
data class Tag(val text: String, val value: String) {
override fun toString(): String = text
}
open class TagFilter(name: String, tags: List<Tag>) : Filter.Select<Tag>(name, tags.toTypedArray()) {
val selected: Tag
get() = values[state]
}
class GenreFilter(genres: List<Tag>) : TagFilter("Gênero", genres)
class StatusFilter(status: List<Tag>) : TagFilter("Status", status)
class SortFilter : Filter.Sort("Ordem", arrayOf("Alfabeticamente"), Selection(0, true))
// [...document.querySelectorAll('#leitor_genero_projeto option')]
// .map(x => `Tag("${x.innerText}", "${x.value}")`)
// .join(',\n')
private fun getGenreList() = listOf(
Tag("Selecione…", ""),
Tag("Ação", "59"),
Tag("Adulto", "63"),
Tag("Artes Marciais", "77"),
Tag("Aventura", "65"),
Tag("Comédia", "30"),
Tag("Drama", "17"),
Tag("Ecchi", "74"),
Tag("Escolar", "64"),
Tag("Esportes", "87"),
Tag("Fantasia", "31"),
Tag("Harem", "82"),
Tag("hentai", "525"),
Tag("Histórico", "95"),
Tag("Josei", "553"),
Tag("Mistério", "19"),
Tag("Oneshot", "527"),
Tag("Psicológico", "20"),
Tag("Romance", "75"),
Tag("Sci-fi", "66"),
Tag("Seinen", "61"),
Tag("Serial Killer", "93"),
Tag("Shoujo", "568"),
Tag("Shoujo Ai", "92"),
Tag("Shounen", "67"),
Tag("Slice Of Life", "94"),
Tag("Sobrenatural", "76"),
Tag("Sobrevivência", "90"),
Tag("Super Poderes", "425"),
Tag("Supernatual", "60"),
Tag("Suspense", "520"),
Tag("Terror", "18"),
Tag("Tragédia", "21"),
Tag("Yuri", "526")
)
// [...document.querySelectorAll('#leitor_status_projeto option')]
// .map(x => `Tag("${x.innerText}", "${x.value}")`)
// .join(',\n')
private fun getStatusList() = listOf(
Tag("Selecione…", ""),
Tag("Cancelado", "6"),
Tag("Em Andamento", "8"),
Tag("Finalizado", "7"),
Tag("One Shot", "4")
)
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used")
override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used")
override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used")
private fun String.toStatus(): Int = when (this) {
"Em Andamento" -> SManga.ONGOING
"Finalizado", "One Shot" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
private data class GoogleDriveResponse(val isValid: Boolean, val code: Int) {
val errorMessage: String
get() = when (code) {
GD_SHARING_RATE_LIMIT_EXCEEDED -> EXCEEDED_GOOGLE_DRIVE_VIEW_LIMIT
else -> GOOGLE_DRIVE_UNAVAILABLE
}
}
companion object {
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36"
private const val EXCEEDED_GOOGLE_DRIVE_VIEW_LIMIT = "Limite de visualizações atingido " +
"no Google Drive. Tente novamente mais tarde."
private const val GOOGLE_DRIVE_UNAVAILABLE = "O Google Drive está indisponível no " +
"momento. Tente novamente mais tarde."
// Reference: https://developers.google.com/drive/api/guides/handle-errors
private const val GD_SHARING_RATE_LIMIT_EXCEEDED = 403
private const val GD_BACKEND_ERROR = 500
}
}