ReadMangas: Theme change (#6525)

* Migration

* Remove data class
This commit is contained in:
Chopper 2024-12-09 09:59:08 -03:00 committed by Draff
parent ac8b118d90
commit d434d470f3
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
8 changed files with 333 additions and 13 deletions

View File

@ -1,9 +1,7 @@
ext { ext {
extName = 'Read Mangas' extName = 'Read Mangas'
extClass = '.ReadMangas' extClass = '.ReadMangas'
themePkg = 'mangathemesia' extVersionCode = 31
baseUrl = 'https://readmangas.org'
overrideVersionCode = 0
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -1,18 +1,289 @@
package eu.kanade.tachiyomi.extension.pt.readmangas package eu.kanade.tachiyomi.extension.pt.readmangas
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import android.annotation.SuppressLint
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient 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 eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.TimeZone
class ReadMangas : MangaThemesia( class ReadMangas() : HttpSource() {
"Read Mangas",
"https://readmangas.org", override val name = "Read Mangas"
"pt-BR",
dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale("pt", "BR")), override val baseUrl = "https://readmangas.org"
) {
override val client: OkHttpClient = super.client.newBuilder() override val lang = "pt-BR"
.rateLimit(3)
override val supportsLatest = true
private val json: Json by injectLazy()
override val client = network.cloudflareClient.newBuilder()
.rateLimit(2)
.build() .build()
override val versionId = 2
// =========================== Popular ================================
private var popularNextCursorPage = ""
override fun popularMangaRequest(page: Int): Request {
if (page == 1) {
popularNextCursorPage = ""
}
val input = buildJsonObject {
put(
"0",
buildJsonObject {
put(
"json",
buildJsonObject {
put("direction", "forward")
if (popularNextCursorPage.isNotBlank()) {
put("cursor", popularNextCursorPage)
}
},
)
},
)
}
val url = "$baseUrl/api/trpc/manga.getAllManga?batch=1".toHttpUrl().newBuilder()
.addQueryParameter("batch", "1")
.addQueryParameter("input", input.toString())
.build()
return GET(url, headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val (mangaPage, nextCursor) = mangasPageParse(response)
popularNextCursorPage = nextCursor
return mangaPage
}
// =========================== Latest ===================================
private var latestNextCursorPage = ""
override fun latestUpdatesRequest(page: Int): Request {
if (page == 1) {
latestNextCursorPage = Date().let { latestUpdateDateFormat.format(it) }
}
val input = buildJsonObject {
put(
"0",
buildJsonObject {
put(
"json",
buildJsonObject {
put("direction", "forward")
put("limit", 20)
put("cursor", latestNextCursorPage)
},
)
},
)
}
val url = "$baseUrl/api/trpc/discover.updated".toHttpUrl().newBuilder()
.addQueryParameter("batch", "1")
.addQueryParameter("input", input.toString())
.build()
return GET(url, headers)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val (mangaPage, nextCursor) = mangasPageParse(response)
latestNextCursorPage = nextCursor
return mangaPage
}
// =========================== Search =================================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/api/trpc/discover.search?batch=1"
val payload = buildJsonObject {
put(
"0",
buildJsonObject {
put(
"json",
buildJsonObject {
put("name", query)
},
)
},
)
}.toString().toRequestBody("application/json".toMediaType())
return POST(url, headers, payload)
}
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
// =========================== Details =================================
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
title = document.selectFirst("h1")!!.text()
thumbnail_url = document.selectFirst("img.w-full")?.absUrl("src")
genre = document.select("div > label + div > div").joinToString { it.text() }
description = document.select("script").map { it.data() }
.firstOrNull { MANGA_DETAILS_DESCRIPTION_REGEX.containsMatchIn(it) }
?.let {
MANGA_DETAILS_DESCRIPTION_REGEX.find(it)?.groups?.get("description")?.value
}
document.selectFirst("div.flex > div.inline-flex.items-center:last-child")?.text()?.let {
status = it.toStatus()
}
}
}
// =========================== Chapter =================================
override fun chapterListRequest(manga: SManga) = throw UnsupportedOperationException()
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
private fun chapterListRequest(manga: SManga, page: Int): Request {
val id = manga.url.substringAfterLast("#")
val input = buildJsonObject {
put(
"1",
buildJsonObject {
put(
"json",
buildJsonObject {
put("id", id)
put("page", page)
put("limit", 10)
put("sort", "desc")
put("search", "")
},
)
},
)
}
val url = "$baseUrl/api/trpc/manga.getLiked,chapter.publicAllChapters".toHttpUrl().newBuilder()
.addQueryParameter("batch", "1")
.addQueryParameter("input", input.toString())
.build()
return GET(url, headers)
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapters = mutableListOf<SChapter>()
var page = 1
do {
val response = client.newCall(this.chapterListRequest(manga, page++)).execute()
val dto = response
.parseAs<List<WrapperResult<ChapterListDto>>>()
.firstNotNullOf { it.result }
.data.json
chapters += chapterListParse(dto.chapters)
} while (dto.hasNext())
return Observable.just(chapters)
}
private fun chapterListParse(chapters: List<ChapterDto>): List<SChapter> {
return chapters.map {
SChapter.create().apply {
name = it.title
chapter_number = it.number.toFloat()
date_upload = it.createdAt.toDate()
url = "/readme/${it.id}"
}
}
}
// =========================== Pages ===================================
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val script = document.select("script").map { it.data() }
.firstOrNull { IMAGE_URL_REGEX.containsMatchIn(it) }
?: return emptyList()
return IMAGE_URL_REGEX.findAll(script).mapIndexed { index, match ->
Page(index, imageUrl = match.groups["imageUrl"]!!.value)
}.toList()
}
override fun imageUrlParse(response: Response) = ""
// =========================== Utilities ===============================
private fun mangasPageParse(response: Response): Pair<MangasPage, String> {
val dto = response.parseAs<List<WrapperResult<MangaListDto>>>().first()
val data = dto.result?.data?.json ?: return MangasPage(emptyList(), false) to ""
val mangas = data.mangas.map {
SManga.create().apply {
title = it.title
thumbnail_url = it.thumbnailUrl
author = it.author
status = it.status.toStatus()
url = "/title/${it.slug}#${it.id}"
}
}
return MangasPage(mangas, data.nextCursor != null) to (data.nextCursor ?: "")
}
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
private fun String.toDate() =
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L }
private fun String.toStatus() = when (lowercase()) {
"ongoing" -> SManga.ONGOING
"hiatus" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
@SuppressLint("SimpleDateFormat")
companion object {
val MANGA_DETAILS_DESCRIPTION_REGEX = """description":(?<description>"[^"]+)""".toRegex()
val IMAGE_URL_REGEX = """url\\":\\"(?<imageUrl>[^(\\")]+)""".toRegex()
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
val latestUpdateDateFormat = SimpleDateFormat(
"EEE MMM dd yyyy HH:mm:ss 'GMT'Z '(Coordinated Universal Time)'",
Locale.ENGLISH,
).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
}
} }

View File

@ -0,0 +1,51 @@
package eu.kanade.tachiyomi.extension.pt.readmangas
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
@Serializable
class WrapperResult<T>(
val result: Result<T>? = null,
) {
@Serializable
class Result<T>(val `data`: Data<T>)
@Serializable
class Data<T>(val json: T)
}
@Serializable
class MangaListDto(
@JsonNames("items")
val mangas: List<MangaDto>,
val nextCursor: String?,
)
@Serializable
class MangaDto(
val author: String,
@SerialName("coverImage")
val thumbnailUrl: String,
val id: String,
val slug: String,
val status: String,
val title: String,
)
@Serializable
class ChapterListDto(
val currentPage: Int,
val chapters: List<ChapterDto>,
val totalPages: Int,
) {
fun hasNext() = currentPage < totalPages
}
@Serializable
class ChapterDto(
val id: String,
val title: String,
val number: String,
val createdAt: String,
)