Migrate HC to Madara. (#11689)

This commit is contained in:
Alessandro Jean 2022-05-01 18:13:08 -03:00 committed by GitHub
parent 0461cd22e2
commit 272d00b352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 17 additions and 290 deletions

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.extension.pt.hipercool
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.multisrc.madara.Madara
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
class Hipercool : Madara("HipercooL", "https://hipercool.xyz", "pt-BR") {
// Migrated from a custom CMS to Madara.
override val versionId = 2
override val client: OkHttpClient = super.client.newBuilder()
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
.build()
}

View File

@ -131,6 +131,7 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("HentaiZone", "https://hentaizone.xyz", "fr", isNsfw = true),
SingleLang("Hentaidexy", "https://hentaidexy.com", "en", isNsfw = true, overrideVersionCode = 2),
SingleLang("Heroz Scanlation", "https://herozscans.com", "en", overrideVersionCode = 1),
SingleLang("HipercooL", "https://hipercool.xyz", "pt-BR", isNsfw = true, className = "Hipercool"),
SingleLang("Hiperdex", "https://hiperdex.com", "en", isNsfw = true, overrideVersionCode = 5),
SingleLang("Hizomanga", "https://hizomanga.com", "ar", overrideVersionCode = 1),
SingleLang("Hscans", "https://hscans.com", "en", overrideVersionCode = 2),

View File

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

View File

@ -1,17 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'HipercooL'
pkgNameSuffix = 'pt.hipercool'
extClass = '.Hipercool'
extVersionCode = 9
isNsfw = true
}
dependencies {
implementation project(':lib-ratelimit')
}
apply from: "$rootDir/common.gradle"

View File

@ -1,227 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.hipercool
import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
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.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
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.util.Locale
class Hipercool : HttpSource() {
// Hardcode the id because the language wasn't specific.
override val id: Long = 5898568703656160
override val name = "HipercooL"
override val baseUrl = "https://hiper.cool"
override val lang = "pt-BR"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(SpecificHostRateLimitInterceptor(baseUrl.toHttpUrl(), 1, 2))
.addInterceptor(SpecificHostRateLimitInterceptor(STATIC_URL.toHttpUrl(), 1, 1))
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", USER_AGENT)
.add("Referer", baseUrl)
.add("X-Requested-With", "XMLHttpRequest")
private val json: Json by injectLazy()
private fun genericMangaListParse(response: Response): MangasPage {
val chapters = response.parseAs<List<HipercoolChapterDto>>()
if (chapters.isEmpty())
return MangasPage(emptyList(), false)
val mangaList = chapters
.distinctBy { it.book!!.title }
.map(::genericMangaFromObject)
val hasNextPage = chapters.size == DEFAULT_COUNT
return MangasPage(mangaList, hasNextPage)
}
private fun genericMangaFromObject(chapter: HipercoolChapterDto): SManga = SManga.create().apply {
title = chapter.book!!.title
thumbnail_url = chapter.book.slug.toThumbnailUrl(chapter.book.revision)
url = "/books/" + chapter.book.slug
}
// The source does not have popular mangas, so use latest instead.
override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page)
override fun popularMangaParse(response: Response): MangasPage = genericMangaListParse(response)
override fun latestUpdatesRequest(page: Int): Request {
val start = (page - 1) * DEFAULT_COUNT
return GET("$baseUrl/api/books/chapters?start=$start&count=$DEFAULT_COUNT", headers)
}
override fun latestUpdatesParse(response: Response): MangasPage = genericMangaListParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val searchPayload = HipercoolSearchDto(
start = (page - 1) * DEFAULT_COUNT,
count = DEFAULT_COUNT,
text = query,
type = "text"
)
val body = json.encodeToString(searchPayload).toRequestBody(JSON_MEDIA_TYPE)
return POST("$baseUrl/api/books/chapters/search", headers, body)
}
override fun searchMangaParse(response: Response): MangasPage = genericMangaListParse(response)
// Workaround to allow "Open in browser" use the real URL.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsApiRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
private fun mangaDetailsApiRequest(manga: SManga): Request {
val slug = manga.url.substringAfterLast("/")
return GET("$baseUrl/api/books/$slug", headers)
}
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
val book = response.parseAs<HipercoolBookDto>()
title = book.title
thumbnail_url = book.slug.toThumbnailUrl(book.revision)
description = book.synopsis.orEmpty()
artist = book.fixedTags["artista"].orEmpty().joinToString("; ")
author = book.fixedTags["autor"].orEmpty().joinToString("; ")
genre = book.fixedTags["tags"].orEmpty().joinToString()
}
// Chapters are available in the same url of the manga details.
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val book = response.parseAs<HipercoolBookDto>()
if (book.chapters is JsonPrimitive)
return emptyList()
return json.decodeFromString<List<HipercoolChapterDto>>(book.chapters.toString())
.map { chapterListItemParse(book, it) }
.reversed()
}
private fun chapterListItemParse(book: HipercoolBookDto, chapter: HipercoolChapterDto): SChapter =
SChapter.create().apply {
name = "Cap. " + chapter.title
chapter_number = chapter.title.toFloatOrNull() ?: -1f
date_upload = chapter.publishedAt.toDate()
scanlator = book.fixedTags["tradutor"]?.joinToString(" & ")
val fullUrl = "$baseUrl/books".toHttpUrl().newBuilder()
.addPathSegment(book.slug)
.addPathSegment(chapter.slug)
.addQueryParameter("images", chapter.images.toString())
.addQueryParameter("revision", book.revision.toString())
.toString()
setUrlWithoutDomain(fullUrl)
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val chapterUrl = (baseUrl + chapter.url).toHttpUrlOrNull()!!
val bookSlug = chapterUrl.pathSegments[1]
val chapterSlug = chapterUrl.pathSegments[2]
val images = chapterUrl.queryParameter("images")!!.toInt()
val revision = chapterUrl.queryParameter("revision")!!
val pages = List(images) { i ->
val imageUrl = "$STATIC_URL/books".toHttpUrl().newBuilder()
.addPathSegment(bookSlug)
.addPathSegment(chapterSlug)
.addPathSegment("$bookSlug-chapter-$chapterSlug-page-${i + 1}.jpg")
.addQueryParameter("revision", revision)
.toString()
Page(i, chapter.url, imageUrl)
}
return Observable.just(pages)
}
override fun pageListParse(response: Response): List<Page> =
throw Exception("This method should not be called!")
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()
.set("Referer", baseUrl + page.url)
.build()
return GET(page.imageUrl!!, newHeaders)
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body?.string().orEmpty())
}
private fun String.toDate(): Long {
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
private fun String.toThumbnailUrl(revision: Int): String =
"$STATIC_URL/books".toHttpUrlOrNull()!!.newBuilder()
.addPathSegment(this)
.addPathSegment("$this-cover.jpg")
.addQueryParameter("revision", revision.toString())
.toString()
companion object {
private const val STATIC_URL = "https://static.hiper.cool"
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaType()
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
private const val DEFAULT_COUNT = 40
private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
}
}
}

View File

@ -1,44 +0,0 @@
package eu.kanade.tachiyomi.extension.pt.hipercool
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable
data class HipercoolBookDto(
val chapters: JsonElement,
val revision: Int = 1,
val slug: String,
val synopsis: String? = null,
val tags: List<HipercoolTagDto> = emptyList(),
val title: String
) {
val fixedTags: Map<String, List<String>>
get() = tags
.groupBy(HipercoolTagDto::slug, HipercoolTagDto::values)
.mapValues { it.value.flatten().map(HipercoolTagDto::label) }
}
@Serializable
data class HipercoolTagDto(
val label: String,
val values: List<HipercoolTagDto> = emptyList(),
val slug: String
)
@Serializable
data class HipercoolChapterDto(
@SerialName("_book") val book: HipercoolBookDto? = null,
val images: Int = 0,
@SerialName("publishied_at") val publishedAt: String,
val slug: String,
val title: String
)
@Serializable
data class HipercoolSearchDto(
val start: Int,
val count: Int,
val text: String,
val type: String
)