Migrate MM from Madara to an individual extension (#16801)

Migrate MM from Madara to an individual extension (closes #16286).
This commit is contained in:
Alessandro Jean 2023-06-20 16:30:52 -03:00 committed by GitHub
parent 749cc27664
commit 06da29ae8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 252 additions and 21 deletions

View File

@ -1,20 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.mizumangas
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MizuMangas : Madara(
"Mizu Mangás",
"https://mizumangas.com.br",
"pt-BR",
SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")),
) {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1, 2, TimeUnit.SECONDS)
.build()
}

View File

@ -304,7 +304,6 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("MiniTwo Scan", "https://minitwoscan.com", "pt-BR"), SingleLang("MiniTwo Scan", "https://minitwoscan.com", "pt-BR"),
SingleLang("Mirad Scanlator", "https://miradscanlator.site", "pt-BR", overrideVersionCode = 1), SingleLang("Mirad Scanlator", "https://miradscanlator.site", "pt-BR", overrideVersionCode = 1),
SingleLang("Mixed Manga", "https://mixedmanga.com", "en", overrideVersionCode = 1), SingleLang("Mixed Manga", "https://mixedmanga.com", "en", overrideVersionCode = 1),
SingleLang("Mizu Mangás", "https://mizumangas.com.br", "pt-BR", isNsfw = true, className = "MizuMangas"),
SingleLang("MMScans", "https://mm-scans.org", "en", overrideVersionCode = 5), SingleLang("MMScans", "https://mm-scans.org", "en", overrideVersionCode = 5),
SingleLang("Momo no Hana Scan", "https://momonohanascan.com", "pt-BR", className = "MomoNoHanaScan", overrideVersionCode = 1), SingleLang("Momo no Hana Scan", "https://momonohanascan.com", "pt-BR", className = "MomoNoHanaScan", overrideVersionCode = 1),
SingleLang("MonarcaManga", "https://monarcamanga.com", "es"), SingleLang("MonarcaManga", "https://monarcamanga.com", "es"),

View File

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

View File

@ -0,0 +1,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Mizu Mangás'
pkgNameSuffix = 'pt.mizumangas'
extClass = '.MizuMangas'
extVersionCode = 30
isNsfw = true
}
apply from: "$rootDir/common.gradle"

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,151 @@
package eu.kanade.tachiyomi.extension.pt.mizumangas
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
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.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
class MizuMangas : HttpSource() {
override val name = "Mizu Mangás"
override val baseUrl = "https://mizumangas.com.br"
override val lang = "pt-BR"
override val supportsLatest = true
// Migrated from Madara to a custom CMS.
override val versionId = 2
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 1, 1, TimeUnit.SECONDS)
.rateLimitHost(API_URL.toHttpUrl(), 1, 1, TimeUnit.SECONDS)
.rateLimitHost(CDN_URL.toHttpUrl(), 1, 2, TimeUnit.SECONDS)
.build()
private val json: Json by injectLazy()
private val apiHeaders: Headers by lazy { apiHeadersBuilder().build() }
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Origin", baseUrl)
.add("Referer", baseUrl)
private fun apiHeadersBuilder(): Headers.Builder = headersBuilder()
.add("Accept", ACCEPT_JSON)
/**
* The site doesn't have a popular section, so we use latest instead.
*/
override fun popularMangaRequest(page: Int) = latestUpdatesRequest(page)
override fun popularMangaParse(response: Response) = latestUpdatesParse(response)
override fun latestUpdatesRequest(page: Int): Request {
return GET("$API_URL/manga?page=$page&per_page=60", apiHeaders)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val result = response.parseAs<MizuMangasPaginatedContent<MizuMangasWorkDto>>()
val workList = result.data.map(MizuMangasWorkDto::toSManga)
return MangasPage(workList, result.hasNextPage)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
// The search with query isn't working in their direct API for some reason,
// so we use their site wrapped API instead for now.
val apiUrl = "$API_URL/search".toHttpUrl().newBuilder()
.addPathSegment(query)
.build()
return GET(apiUrl, apiHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
val result = response.parseAs<MizuMangasSearchDto>()
val workList = result.mangas.map(MizuMangasWorkDto::toSManga)
return MangasPage(workList, hasNextPage = false)
}
override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url
override fun mangaDetailsRequest(manga: SManga): Request {
val id = manga.url.substringAfter("/manga/")
return GET("$API_URL/manga/$id", apiHeaders)
}
override fun mangaDetailsParse(response: Response): SManga {
return response.parseAs<MizuMangasWorkDto>().toSManga()
}
override fun chapterListRequest(manga: SManga): Request {
val id = manga.url.substringAfter("/manga/")
return GET("$API_URL/chapter/manga/all/$id", apiHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
return response.parseAs<List<MizuMangasChapterDto>>()
.map(MizuMangasChapterDto::toSChapter)
}
override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url
override fun pageListRequest(chapter: SChapter): Request {
val id = chapter.url.substringAfter("/reader/")
return GET("$API_URL/chapter/$id")
}
override fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<MizuMangasChapterDto>()
val chapterUrl = "$baseUrl/manga/reader/${result.id}"
return result.pages.mapIndexed { i, pageDto ->
Page(i, chapterUrl, "$CDN_URL/${pageDto.page}")
}
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
override fun imageRequest(page: Page): Request {
val newHeaders = headersBuilder()
.add("Accept", ACCEPT_IMAGE)
.set("Referer", page.url)
.build()
return GET(page.imageUrl!!, newHeaders)
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body.string())
}
companion object {
private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
private const val ACCEPT_JSON = "application/json"
private const val API_URL = "https://api.mizumangas.com.br"
const val CDN_URL = "https://cdn.mizumangas.com.br"
}
}

View File

@ -0,0 +1,85 @@
package eu.kanade.tachiyomi.extension.pt.mizumangas
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
@Serializable
data class MizuMangasPaginatedContent<T>(
@SerialName("current_page") val currentPage: Int,
val data: List<T> = emptyList(),
@SerialName("last_page") val lastPage: Int,
) {
val hasNextPage: Boolean
get() = currentPage < lastPage
}
@Serializable
data class MizuMangasSearchDto(val mangas: List<MizuMangasWorkDto>)
@Serializable
data class MizuMangasWorkDto(
val id: Int,
val photo: String? = null,
val synopsis: String? = null,
val name: String,
val status: MizuMangasStatusDto? = null,
val categories: List<MizuMangasCategoryDto> = emptyList(),
val people: List<MizuMangasStaffDto> = emptyList(),
) {
fun toSManga(): SManga = SManga.create().apply {
title = name
author = people.joinToString { it.name }
description = synopsis
genre = categories.joinToString { it.name }
status = when (this@MizuMangasWorkDto.status?.name) {
"Ativo" -> SManga.ONGOING
"Completo" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = "${MizuMangas.CDN_URL}/$photo"
url = "/manga/$id"
}
}
@Serializable
data class MizuMangasStatusDto(val name: String)
@Serializable
data class MizuMangasCategoryDto(val name: String)
@Serializable
data class MizuMangasStaffDto(val name: String)
@Serializable
data class MizuMangasChapterDto(
val id: Int,
@SerialName("chapter") val number: String,
@SerialName("created_at") val createdAt: String? = null,
@SerialName("manga_pages") val pages: List<MizuMangasPageDto> = emptyList(),
) {
fun toSChapter(): SChapter = SChapter.create().apply {
name = "Capítulo $number"
chapter_number = number.toFloatOrNull() ?: -1f
date_upload = runCatching { DATE_FORMATTER.parse(createdAt!!)?.time }
.getOrNull() ?: 0L
url = "/manga/reader/$id"
}
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.US)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
}
}
}
@Serializable
data class MizuMangasPageDto(val page: String)