GekkouScans: Migrate theme (#8275)

Migrate theme
This commit is contained in:
Chopper 2025-03-31 11:39:23 -03:00 committed by Draff
parent 7fcbbec4c7
commit d0d9558eb2
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 223 additions and 17 deletions

View File

@ -1,10 +1,8 @@
ext { ext {
extName = 'Gekkou Scans' extName = 'Gekkou Scans'
extClass = '.GekkouScans' extClass = '.GekkouScans'
themePkg = 'madara' extVersionCode = 42
baseUrl = 'https://gekkou.space' isNsfw = true
overrideVersionCode = 0
isNsfw = false
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,21 +1,143 @@
package eu.kanade.tachiyomi.extension.pt.gekkouscans package eu.kanade.tachiyomi.extension.pt.gekkouscans
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import java.text.SimpleDateFormat import eu.kanade.tachiyomi.source.model.FilterList
import java.util.Locale 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 keiyoushi.utils.parseAs
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.http.HTTP_FORBIDDEN
import java.io.IOException
class GekkouScans : HttpSource() {
override val name: String = "Gekkou Scans"
override val baseUrl: String = "https://new.gekkou.space"
private val apiUrl = "$baseUrl/api"
override val lang: String = "pt-BR"
override val supportsLatest: Boolean = true
// Moved from Madara
override val versionId: Int = 2
class GekkouScans : Madara(
"Gekkou Scans",
"https://gekkou.space",
"pt-BR",
SimpleDateFormat("dd 'de' MMM 'de' yyyy", Locale("pt", "BR")),
) {
override val client = super.client.newBuilder() override val client = super.client.newBuilder()
.rateLimit(3) .rateLimitHost(apiUrl.toHttpUrl(), 2, 1)
.addInterceptor(::verifyLogin)
.build() .build()
override val useNewChapterEndpoint = true // ========================= Popular ====================================
override val useLoadMoreRequest = LoadMoreStrategy.Never override fun popularMangaRequest(page: Int): Request = GET("$apiUrl/manga/todos", headers)
override fun popularMangaParse(response: Response): MangasPage {
val mangas = response.parseAs<List<MangaDto>>()
.sortedByDescending(MangaDto::popular)
.map(MangaDto::toSManga)
return MangasPage(mangas, false)
}
// ========================= Latest =====================================
override fun latestUpdatesRequest(page: Int): Request =
GET("$apiUrl/manga/recent-updates", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val mangas = response.parseAs<List<LatestMangaDto>>().map {
SManga.create().apply {
title = it.name
url = "/projeto/${it.slug}"
thumbnail_url = it.thumbnailUrl
}
}
return MangasPage(mangas, false)
}
// ========================= Search =====================================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$apiUrl/manga/search".toHttpUrl().newBuilder()
.setQueryParameter("query", query)
.setQueryParameter("limit", "10")
.build()
return GET(url, headers)
}
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
// ========================= Details ====================================
override fun getMangaUrl(manga: SManga) = "$baseUrl/${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request {
val slug = manga.url.substringAfterLast("/")
return GET("$apiUrl/manga/$slug", headers)
}
override fun mangaDetailsParse(response: Response): SManga =
response.parseAs<MangaDto>().let(MangaDto::toSManga)
// ========================= Chapters ===================================
override fun getChapterUrl(chapter: SChapter): String = "$baseUrl/${chapter.url}"
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> =
response.parseAs<MangaDto>().toListSChapter().sortedByDescending(SChapter::chapter_number)
// ========================= Pages ======================================
override fun pageListRequest(chapter: SChapter): Request {
val pathSegment = chapter.url.split("/").filter(String::isNotBlank)
.drop(1).joinToString("/")
return addRequestRequireSettings("$apiUrl/chapter/$pathSegment".toHttpUrl())
}
override fun pageListParse(response: Response): List<Page> {
return response.parseAs<PagesDto>().pages.sortedBy(PagesDto.ImageUrl::index).map {
val imageUrl = "$apiUrl${it.url}"
Page(it.index, imageUrl = imageUrl)
}
}
override fun imageRequest(page: Page): Request {
return addRequestRequireSettings(super.imageRequest(page).url)
}
override fun imageUrlParse(response: Response): String = ""
// ========================= Utilities ======================================
private fun addRequestRequireSettings(url: HttpUrl): Request {
val newUrl = url.newBuilder()
.addQueryParameter("cb", unixTime().toString())
.build()
// It's possible to add a real user here.
val newHeaders = headers.newBuilder()
.set("User-Id", (1..5000).random().toString())
.build()
return GET(newUrl, newHeaders)
}
private fun verifyLogin(chain: Interceptor.Chain): Response =
chain.proceed(chain.request()).takeIf { it.code != HTTP_FORBIDDEN } ?: throw IOException("Faça o login na WebView")
private fun unixTime(): Int {
val timestampMillis = System.currentTimeMillis()
return (timestampMillis / 1000).toInt()
}
} }

View File

@ -0,0 +1,86 @@
package eu.kanade.tachiyomi.extension.pt.gekkouscans
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
private val CDN_URL = "https://usc1.contabostorage.com/783e4d097dbf4f83aefe59be94798c82:gekkou"
@Serializable
class MangaDto(
val slug: String,
val name: String,
val artists: String,
val author: String,
val genres: List<String>,
val status: String,
val popular: Boolean,
val summary: String,
val chapters: List<ChapterDto>,
private val urlCover: String,
) {
val thumbnailUrl: String get() = getAbsoluteThumbnailUrl(urlCover)
fun toSManga() = SManga.create().apply {
title = name
url = "/projeto/$slug"
description = summary
genre = genres.joinToString()
artist = artists
author = this@MangaDto.author
initialized = true
status = when (this@MangaDto.status.lowercase()) {
"completo" -> SManga.COMPLETED
"ativo" -> SManga.ONGOING
"cancelado" -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
thumbnail_url = thumbnailUrl
}
fun toListSChapter(): List<SChapter> {
return chapters.map {
SChapter.create().apply {
name = it.chapterNumber
chapter_number = it.chapterNumber.toFloat()
url = "/leitor/$slug/${it.chapterNumber}"
}
}
}
}
@Serializable
class ChapterDto(
@SerialName("chapterSlug")
val chapterNumber: String,
)
@Serializable
class LatestMangaDto(
@SerialName("mangaSlug")
val slug: String,
val name: String,
private val urlCover: String,
) {
val thumbnailUrl: String get() = getAbsoluteThumbnailUrl(urlCover)
}
@Serializable
class PagesDto(
val pages: List<ImageUrl>,
) {
@Serializable
class ImageUrl(
@SerialName("pageNumber")
val index: Int,
val url: String,
)
}
fun getAbsoluteThumbnailUrl(urlCover: String): String {
return when {
urlCover.startsWith("http", ignoreCase = true) -> urlCover
else -> "$CDN_URL/$urlCover"
}
}