Add SenshiManga (#2484)

* Add SenshiManga

* Move to individual

* Today I learned template class
This commit is contained in:
bapeey 2024-04-20 10:27:54 -05:00 committed by Draff
parent 3ea687e183
commit a6c35d3175
8 changed files with 231 additions and 0 deletions

View File

@ -0,0 +1,8 @@
ext {
extName = 'Senshi Manga'
extClass = '.SenshiManga'
extVersionCode = 1
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,145 @@
package eu.kanade.tachiyomi.extension.es.senshimanga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
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.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 uy.kohesive.injekt.injectLazy
class SenshiManga : HttpSource() {
override val name = "Senshi Manga"
override val baseUrl = "https://senshimanga.com"
override val lang = "es"
override val supportsLatest = true
private val apiBaseUrl = "https://lat-manga.com"
private val json: Json by injectLazy()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 3)
.rateLimitHost(apiBaseUrl.toHttpUrl(), 3)
.build()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val apiHeaders: Headers = headersBuilder()
.add("Organization-Domain", "senshimanga.com")
.build()
override fun popularMangaRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=popular", apiHeaders)
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
override fun latestUpdatesRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=latest", apiHeaders)
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$apiBaseUrl/api/manga-custom".toHttpUrl().newBuilder()
url.setQueryParameter("page", page.toString())
url.setQueryParameter("limit", PAGE_LIMIT.toString())
filters.forEach { filter ->
when (filter) {
is SortByFilter -> url.setQueryParameter("order", filter.toUriPart())
else -> {}
}
}
if (query.isNotBlank()) url.setQueryParameter("q", query)
return GET(url.build(), apiHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
val page = response.request.url.queryParameter("page")!!.toInt()
val result = json.decodeFromString<Data<SeriesListDataDto>>(response.body.string())
val mangas = result.data.series.map { it.toSManga() }
val hasNextPage = page < result.data.maxPage
return MangasPage(mangas, hasNextPage)
}
override fun getFilterList() = FilterList(
SortByFilter("Ordenar por", getSortList()),
)
private fun getSortList() = arrayOf(
Pair("Popularidad", "popular"),
Pair("Recientes", "latest"),
)
override fun getMangaUrl(manga: SManga): String = "$baseUrl/manga/${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request =
GET("$apiBaseUrl/api/manga-custom/${manga.url}", apiHeaders)
override fun mangaDetailsParse(response: Response): SManga {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
return result.data.toSMangaDetails()
}
override fun getChapterUrl(chapter: SChapter): String {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return "$baseUrl/manga/$seriesSlug/chapters/$chapterSlug"
}
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
val seriesSlug = result.data.slug
return result.data.chapters?.map { it.toSChapter(seriesSlug) } ?: emptyList()
}
override fun pageListRequest(chapter: SChapter): Request {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return GET("$apiBaseUrl/api/manga-custom/$seriesSlug/chapter/$chapterSlug/pages", apiHeaders)
}
override fun pageListParse(response: Response): List<Page> {
val result = json.decodeFromString<Data<List<PageDto>>>(response.body.string())
return result.data.mapIndexed { i, page ->
Page(i, imageUrl = page.imageUrl)
}
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
class SortByFilter(title: String, list: Array<Pair<String, String>>) : UriPartFilter(title, list)
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
}
companion object {
private const val PAGE_LIMIT = 36
}
}

View File

@ -0,0 +1,78 @@
package eu.kanade.tachiyomi.extension.es.senshimanga
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.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
class Data<T>(val data: T)
@Serializable
class SeriesListDataDto(
@SerialName("data") val series: List<SeriesDto> = emptyList(),
val maxPage: Int = 0,
)
@Serializable
class SeriesDto(
val slug: String,
private val imageUrl: String,
private val title: String,
private val status: String? = null,
private val description: String? = null,
private val authors: List<SeriesAuthorDto>? = emptyList(),
val chapters: List<SeriesChapterDto>? = emptyList(),
) {
fun toSManga() = SManga.create().apply {
title = this@SeriesDto.title
thumbnail_url = imageUrl
url = slug
}
fun toSMangaDetails() = toSManga().apply {
status = parseStatus(this@SeriesDto.status)
description = this@SeriesDto.description
title = this@SeriesDto.title
author = authors?.joinToString { it.name }
}
private fun parseStatus(status: String?) = when (status) {
"ongoing" -> SManga.ONGOING
"hiatus" -> SManga.ON_HIATUS
"finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
@Serializable
class SeriesAuthorDto(
val name: String,
)
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
@Serializable
class SeriesChapterDto(
private val title: String,
private val number: Float,
private val createdAt: String,
) {
fun toSChapter(seriesSlug: String) = SChapter.create().apply {
name = "Capítulo ${number.toString().removeSuffix(".0")} - $title"
date_upload = try {
dateFormat.parse(createdAt)?.time ?: 0L
} catch (_: ParseException) {
0L
}
url = "$seriesSlug/$number"
}
}
@Serializable
class PageDto(
val imageUrl: String,
)