Refactor the code of some extensions. (#10313)
This commit is contained in:
parent
5cb8df48af
commit
418cded260
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'Argos Scan'
|
extName = 'Argos Scan'
|
||||||
pkgNameSuffix = 'pt.argosscan'
|
pkgNameSuffix = 'pt.argosscan'
|
||||||
extClass = '.ArgosScan'
|
extClass = '.ArgosScan'
|
||||||
extVersionCode = 18
|
extVersionCode = 19
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -16,7 +16,9 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.add
|
import kotlinx.serialization.json.add
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
@ -83,14 +85,13 @@ class ArgosScan : HttpSource(), ConfigurableSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val result = json.parseToJsonElement(response.body!!.string()).jsonObject
|
val result = response.parseAs<ArgosResponseDto<ArgosProjectListDto>>()
|
||||||
|
|
||||||
if (result["errors"] != null) {
|
if (result.data == null) {
|
||||||
throw Exception(REQUEST_ERROR)
|
throw Exception(REQUEST_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
val projectList = result["data"]!!.jsonObject["getProjects"]!!
|
val projectList = result.data["getProjects"]!!
|
||||||
.let { json.decodeFromJsonElement<ArgosProjectListDto>(it) }
|
|
||||||
|
|
||||||
val mangaList = projectList.projects
|
val mangaList = projectList.projects
|
||||||
.map(::genericMangaFromObject)
|
.map(::genericMangaFromObject)
|
||||||
|
@ -155,36 +156,32 @@ class ArgosScan : HttpSource(), ConfigurableSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||||
val result = json.parseToJsonElement(response.body!!.string()).jsonObject
|
val result = response.parseAs<ArgosResponseDto<ArgosProjectDto>>()
|
||||||
|
|
||||||
if (result["errors"] != null) {
|
if (result.data == null) {
|
||||||
throw Exception(REQUEST_ERROR)
|
throw Exception(REQUEST_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
val project = result["data"]!!.jsonObject["project"]!!.jsonObject
|
val project = result.data["project"]!!
|
||||||
.let { json.decodeFromJsonElement<ArgosProjectDto>(it) }
|
|
||||||
|
|
||||||
title = project.name!!
|
title = project.name!!
|
||||||
thumbnail_url = "$baseUrl/images/${project.id}/${project.cover!!}"
|
thumbnail_url = "$baseUrl/images/${project.id}/${project.cover!!}"
|
||||||
description = project.description.orEmpty()
|
description = project.description.orEmpty()
|
||||||
author = project.authors.orEmpty().joinToString(", ")
|
author = project.authors.orEmpty().joinToString()
|
||||||
status = SManga.ONGOING
|
status = SManga.ONGOING
|
||||||
genre = project.tags.orEmpty().joinToString(", ") { it.name }
|
genre = project.tags.orEmpty().sortedBy(ArgosTagDto::name).joinToString { it.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
|
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val result = json.parseToJsonElement(response.body!!.string()).jsonObject
|
val result = response.parseAs<ArgosResponseDto<ArgosProjectDto>>()
|
||||||
|
|
||||||
if (result["errors"] != null) {
|
if (result.data == null) {
|
||||||
throw Exception(REQUEST_ERROR)
|
throw Exception(REQUEST_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
val project = result["data"]!!.jsonObject["project"]!!.jsonObject
|
return result.data["project"]!!.chapters.map(::chapterFromObject)
|
||||||
.let { json.decodeFromJsonElement<ArgosProjectDto>(it) }
|
|
||||||
|
|
||||||
return project.chapters.map(::chapterFromObject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterFromObject(chapter: ArgosChapterDto): SChapter = SChapter.create().apply {
|
private fun chapterFromObject(chapter: ArgosChapterDto): SChapter = SChapter.create().apply {
|
||||||
|
@ -211,7 +208,7 @@ class ArgosScan : HttpSource(), ConfigurableSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val result = json.parseToJsonElement(response.body!!.string()).jsonObject
|
val result = response.parseAs<JsonElement>().jsonObject
|
||||||
|
|
||||||
if (result["errors"] != null) {
|
if (result["errors"] != null) {
|
||||||
throw Exception(REQUEST_ERROR)
|
throw Exception(REQUEST_ERROR)
|
||||||
|
@ -345,6 +342,10 @@ class ArgosScan : HttpSource(), ConfigurableSource {
|
||||||
return POST(GRAPHQL_URL, newHeaders, body)
|
return POST(GRAPHQL_URL, newHeaders, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAs(): T = use {
|
||||||
|
json.decodeFromString(it.body?.string().orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
private fun String.toDate(): Long {
|
private fun String.toDate(): Long {
|
||||||
return runCatching { DATE_PARSER.parse(this)?.time }
|
return runCatching { DATE_PARSER.parse(this)?.time }
|
||||||
.getOrNull() ?: 0L
|
.getOrNull() ?: 0L
|
||||||
|
|
|
@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.extension.pt.argosscan
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ArgosResponseDto<T>(
|
||||||
|
val data: Map<String, T>? = null
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ArgosProjectListDto(
|
data class ArgosProjectListDto(
|
||||||
val count: Int = 0,
|
val count: Int = 0,
|
||||||
|
|
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'HipercooL'
|
extName = 'HipercooL'
|
||||||
pkgNameSuffix = 'pt.hipercool'
|
pkgNameSuffix = 'pt.hipercool'
|
||||||
extClass = '.Hipercool'
|
extClass = '.Hipercool'
|
||||||
extVersionCode = 8
|
extVersionCode = 9
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.hipercool
|
package eu.kanade.tachiyomi.extension.pt.hipercool
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
|
import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
@ -11,23 +11,21 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.ParseException
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class Hipercool : HttpSource() {
|
class Hipercool : HttpSource() {
|
||||||
|
|
||||||
|
@ -43,7 +41,8 @@ class Hipercool : HttpSource() {
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
|
.addInterceptor(SpecificHostRateLimitInterceptor(baseUrl.toHttpUrl(), 1, 2))
|
||||||
|
.addInterceptor(SpecificHostRateLimitInterceptor(STATIC_URL.toHttpUrl(), 1, 1))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
|
@ -54,14 +53,14 @@ class Hipercool : HttpSource() {
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private fun genericMangaListParse(response: Response): MangasPage {
|
private fun genericMangaListParse(response: Response): MangasPage {
|
||||||
val chapters = json.decodeFromString<List<HipercoolChapterDto>>(response.body!!.string())
|
val chapters = response.parseAs<List<HipercoolChapterDto>>()
|
||||||
|
|
||||||
if (chapters.isEmpty())
|
if (chapters.isEmpty())
|
||||||
return MangasPage(emptyList(), false)
|
return MangasPage(emptyList(), false)
|
||||||
|
|
||||||
val mangaList = chapters
|
val mangaList = chapters
|
||||||
|
.distinctBy { it.book!!.title }
|
||||||
.map(::genericMangaFromObject)
|
.map(::genericMangaFromObject)
|
||||||
.distinctBy { it.title }
|
|
||||||
|
|
||||||
val hasNextPage = chapters.size == DEFAULT_COUNT
|
val hasNextPage = chapters.size == DEFAULT_COUNT
|
||||||
|
|
||||||
|
@ -87,17 +86,14 @@ class Hipercool : HttpSource() {
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = genericMangaListParse(response)
|
override fun latestUpdatesParse(response: Response): MangasPage = genericMangaListParse(response)
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val mediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
|
val searchPayload = HipercoolSearchDto(
|
||||||
|
start = (page - 1) * DEFAULT_COUNT,
|
||||||
|
count = DEFAULT_COUNT,
|
||||||
|
text = query,
|
||||||
|
type = "text"
|
||||||
|
)
|
||||||
|
|
||||||
// Create json body.
|
val body = json.encodeToString(searchPayload).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
val json = buildJsonObject {
|
|
||||||
put("start", (page - 1) * DEFAULT_COUNT)
|
|
||||||
put("content", DEFAULT_COUNT)
|
|
||||||
put("text", query)
|
|
||||||
put("type", "text")
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = json.toString().toRequestBody(mediaType)
|
|
||||||
|
|
||||||
return POST("$baseUrl/api/books/chapters/search", headers, body)
|
return POST("$baseUrl/api/books/chapters/search", headers, body)
|
||||||
}
|
}
|
||||||
|
@ -119,39 +115,22 @@ class Hipercool : HttpSource() {
|
||||||
return GET("$baseUrl/api/books/$slug", headers)
|
return GET("$baseUrl/api/books/$slug", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||||
val book = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
|
val book = response.parseAs<HipercoolBookDto>()
|
||||||
|
|
||||||
val artists = book.tags
|
|
||||||
.filter { it.label == "Artista" }
|
|
||||||
.flatMap { it.values }
|
|
||||||
.joinToString("; ") { it.label }
|
|
||||||
|
|
||||||
val authors = book.tags
|
|
||||||
.filter { it.label == "Autor" }
|
|
||||||
.flatMap { it.values }
|
|
||||||
.joinToString("; ") { it.label }
|
|
||||||
|
|
||||||
val tags = book.tags
|
|
||||||
.filter { it.label == "Tags" }
|
|
||||||
.flatMap { it.values }
|
|
||||||
.joinToString { it.label }
|
|
||||||
|
|
||||||
return SManga.create().apply {
|
|
||||||
title = book.title
|
title = book.title
|
||||||
thumbnail_url = book.slug.toThumbnailUrl(book.revision)
|
thumbnail_url = book.slug.toThumbnailUrl(book.revision)
|
||||||
description = book.synopsis.orEmpty()
|
description = book.synopsis.orEmpty()
|
||||||
artist = artists
|
artist = book.fixedTags["artista"].orEmpty().joinToString("; ")
|
||||||
author = authors
|
author = book.fixedTags["autor"].orEmpty().joinToString("; ")
|
||||||
genre = tags
|
genre = book.fixedTags["tags"].orEmpty().joinToString()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters are available in the same url of the manga details.
|
// Chapters are available in the same url of the manga details.
|
||||||
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
|
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val book = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
|
val book = response.parseAs<HipercoolBookDto>()
|
||||||
|
|
||||||
if (book.chapters is JsonPrimitive)
|
if (book.chapters is JsonPrimitive)
|
||||||
return emptyList()
|
return emptyList()
|
||||||
|
@ -166,8 +145,9 @@ class Hipercool : HttpSource() {
|
||||||
name = "Cap. " + chapter.title
|
name = "Cap. " + chapter.title
|
||||||
chapter_number = chapter.title.toFloatOrNull() ?: -1f
|
chapter_number = chapter.title.toFloatOrNull() ?: -1f
|
||||||
date_upload = chapter.publishedAt.toDate()
|
date_upload = chapter.publishedAt.toDate()
|
||||||
|
scanlator = book.fixedTags["tradutor"]?.joinToString(" & ")
|
||||||
|
|
||||||
val fullUrl = "$baseUrl/books".toHttpUrlOrNull()!!.newBuilder()
|
val fullUrl = "$baseUrl/books".toHttpUrl().newBuilder()
|
||||||
.addPathSegment(book.slug)
|
.addPathSegment(book.slug)
|
||||||
.addPathSegment(chapter.slug)
|
.addPathSegment(chapter.slug)
|
||||||
.addQueryParameter("images", chapter.images.toString())
|
.addQueryParameter("images", chapter.images.toString())
|
||||||
|
@ -183,30 +163,26 @@ class Hipercool : HttpSource() {
|
||||||
val bookSlug = chapterUrl.pathSegments[1]
|
val bookSlug = chapterUrl.pathSegments[1]
|
||||||
val chapterSlug = chapterUrl.pathSegments[2]
|
val chapterSlug = chapterUrl.pathSegments[2]
|
||||||
val images = chapterUrl.queryParameter("images")!!.toInt()
|
val images = chapterUrl.queryParameter("images")!!.toInt()
|
||||||
val revision = chapterUrl.queryParameter("revision")!!.toInt()
|
val revision = chapterUrl.queryParameter("revision")!!
|
||||||
|
|
||||||
val pages = arrayListOf<Page>()
|
val pages = List(images) { i ->
|
||||||
|
val imageUrl = "$STATIC_URL/books".toHttpUrl().newBuilder()
|
||||||
// Create the pages.
|
|
||||||
for (i in 1..images) {
|
|
||||||
val imageUrl = "$STATIC_URL/books".toHttpUrlOrNull()!!.newBuilder()
|
|
||||||
.addPathSegment(bookSlug)
|
.addPathSegment(bookSlug)
|
||||||
.addPathSegment(chapterSlug)
|
.addPathSegment(chapterSlug)
|
||||||
.addPathSegment("$bookSlug-chapter-$chapterSlug-page-$i.jpg")
|
.addPathSegment("$bookSlug-chapter-$chapterSlug-page-${i + 1}.jpg")
|
||||||
.addQueryParameter("revision", revision.toString())
|
.addQueryParameter("revision", revision)
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
pages += Page(i - 1, chapter.url, imageUrl)
|
Page(i, chapter.url, imageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observable.just(pages)
|
return Observable.just(pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> = throw Exception("This method should not be called!")
|
override fun pageListParse(response: Response): List<Page> =
|
||||||
|
throw Exception("This method should not be called!")
|
||||||
|
|
||||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
|
||||||
return Observable.just(page.imageUrl!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = ""
|
override fun imageUrlParse(response: Response): String = ""
|
||||||
|
|
||||||
|
@ -218,12 +194,13 @@ class Hipercool : HttpSource() {
|
||||||
return GET(page.imageUrl!!, newHeaders)
|
return GET(page.imageUrl!!, newHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toDate(): Long {
|
private inline fun <reified T> Response.parseAs(): T = use {
|
||||||
return try {
|
json.decodeFromString(it.body?.string().orEmpty())
|
||||||
DATE_FORMATTER.parse(substringBefore("T"))?.time ?: 0L
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
0L
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toDate(): Long {
|
||||||
|
return runCatching { DATE_FORMATTER.parse(this)?.time }
|
||||||
|
.getOrNull() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toThumbnailUrl(revision: Int): String =
|
private fun String.toThumbnailUrl(revision: Int): String =
|
||||||
|
@ -236,11 +213,15 @@ class Hipercool : HttpSource() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val STATIC_URL = "https://static.hiper.cool"
|
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) " +
|
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
|
||||||
|
|
||||||
private const val DEFAULT_COUNT = 40
|
private const val DEFAULT_COUNT = 40
|
||||||
|
|
||||||
private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
|
private val DATE_FORMATTER by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,18 @@ data class HipercoolBookDto(
|
||||||
val synopsis: String? = null,
|
val synopsis: String? = null,
|
||||||
val tags: List<HipercoolTagDto> = emptyList(),
|
val tags: List<HipercoolTagDto> = emptyList(),
|
||||||
val title: String
|
val title: String
|
||||||
)
|
) {
|
||||||
|
val fixedTags: Map<String, List<String>>
|
||||||
|
get() = tags
|
||||||
|
.groupBy(HipercoolTagDto::slug, HipercoolTagDto::values)
|
||||||
|
.mapValues { it.value.flatten().map(HipercoolTagDto::label) }
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class HipercoolTagDto(
|
data class HipercoolTagDto(
|
||||||
val label: String,
|
val label: String,
|
||||||
val values: List<HipercoolTagDto> = emptyList()
|
val values: List<HipercoolTagDto> = emptyList(),
|
||||||
|
val slug: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -28,3 +34,11 @@ data class HipercoolChapterDto(
|
||||||
val slug: String,
|
val slug: String,
|
||||||
val title: String
|
val title: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HipercoolSearchDto(
|
||||||
|
val start: Int,
|
||||||
|
val count: Int,
|
||||||
|
val text: String,
|
||||||
|
val type: String
|
||||||
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'Saikai Scan'
|
extName = 'Saikai Scan'
|
||||||
pkgNameSuffix = 'pt.saikaiscan'
|
pkgNameSuffix = 'pt.saikaiscan'
|
||||||
extClass = '.SaikaiScan'
|
extClass = '.SaikaiScan'
|
||||||
extVersionCode = 7
|
extVersionCode = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.saikaiscan
|
package eu.kanade.tachiyomi.extension.pt.saikaiscan
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
|
import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -20,10 +19,8 @@ import okhttp3.Response
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.ParseException
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class SaikaiScan : HttpSource() {
|
class SaikaiScan : HttpSource() {
|
||||||
|
|
||||||
|
@ -36,7 +33,8 @@ class SaikaiScan : HttpSource() {
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS))
|
.addInterceptor(SpecificHostRateLimitInterceptor(API_URL.toHttpUrl(), 1, 2))
|
||||||
|
.addInterceptor(SpecificHostRateLimitInterceptor(IMAGE_SERVER_URL.toHttpUrl(), 1, 1))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
@ -63,7 +61,7 @@ class SaikaiScan : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val result = json.decodeFromString<SaikaiScanPaginatedStoriesDto>(response.body!!.string())
|
val result = response.parseAs<SaikaiScanPaginatedStoriesDto>()
|
||||||
|
|
||||||
val mangaList = result.data!!.map(::popularMangaFromObject)
|
val mangaList = result.data!!.map(::popularMangaFromObject)
|
||||||
val hasNextPage = result.meta!!.currentPage < result.meta.lastPage
|
val hasNextPage = result.meta!!.currentPage < result.meta.lastPage
|
||||||
|
@ -170,7 +168,7 @@ class SaikaiScan : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||||
val result = json.decodeFromString<SaikaiScanPaginatedStoriesDto>(response.body!!.string())
|
val result = response.parseAs<SaikaiScanPaginatedStoriesDto>()
|
||||||
val story = result.data!![0]
|
val story = result.data!![0]
|
||||||
|
|
||||||
title = story.title
|
title = story.title
|
||||||
|
@ -202,13 +200,13 @@ class SaikaiScan : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val result = json.decodeFromString<SaikaiScanPaginatedStoriesDto>(response.body!!.string())
|
val result = response.parseAs<SaikaiScanPaginatedStoriesDto>()
|
||||||
val story = result.data!![0]
|
val story = result.data!![0]
|
||||||
|
|
||||||
return story.releases
|
return story.releases
|
||||||
.filter { it.isActive == 1 }
|
.filter { it.isActive == 1 }
|
||||||
.map { chapterFromObject(it, story.slug) }
|
.map { chapterFromObject(it, story.slug) }
|
||||||
.sortedByDescending { it.chapter_number }
|
.sortedByDescending(SChapter::chapter_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterFromObject(obj: SaikaiScanReleaseDto, storySlug: String): SChapter =
|
private fun chapterFromObject(obj: SaikaiScanReleaseDto, storySlug: String): SChapter =
|
||||||
|
@ -216,7 +214,7 @@ class SaikaiScan : HttpSource() {
|
||||||
name = "Capítulo ${obj.chapter}" +
|
name = "Capítulo ${obj.chapter}" +
|
||||||
(if (obj.title.isNullOrEmpty().not()) " - ${obj.title}" else "")
|
(if (obj.title.isNullOrEmpty().not()) " - ${obj.title}" else "")
|
||||||
chapter_number = obj.chapter.toFloatOrNull() ?: -1f
|
chapter_number = obj.chapter.toFloatOrNull() ?: -1f
|
||||||
date_upload = obj.publishedAt.substringBefore(" ").toDate()
|
date_upload = obj.publishedAt.toDate()
|
||||||
scanlator = this@SaikaiScan.name
|
scanlator = this@SaikaiScan.name
|
||||||
url = "/ler/comics/$storySlug/${obj.id}/${obj.slug}"
|
url = "/ler/comics/$storySlug/${obj.id}/${obj.slug}"
|
||||||
}
|
}
|
||||||
|
@ -238,7 +236,7 @@ class SaikaiScan : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val result = json.decodeFromString<SaikaiScanReleaseResultDto>(response.body!!.string())
|
val result = response.parseAs<SaikaiScanReleaseResultDto>()
|
||||||
|
|
||||||
return result.data!!.releaseImages.mapIndexed { i, obj ->
|
return result.data!!.releaseImages.mapIndexed { i, obj ->
|
||||||
Page(i, "", "$IMAGE_SERVER_URL/${obj.image}")
|
Page(i, "", "$IMAGE_SERVER_URL/${obj.image}")
|
||||||
|
@ -257,43 +255,6 @@ class SaikaiScan : HttpSource() {
|
||||||
return GET(page.imageUrl!!, imageHeaders)
|
return GET(page.imageUrl!!, imageHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Genre(title: String, val id: Int) : Filter.CheckBox(title)
|
|
||||||
|
|
||||||
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres)
|
|
||||||
|
|
||||||
private data class Country(val name: String, val id: Int) {
|
|
||||||
override fun toString(): String = name
|
|
||||||
}
|
|
||||||
|
|
||||||
private open class EnhancedSelect<T>(name: String, values: Array<T>) : Filter.Select<T>(name, values) {
|
|
||||||
val selected: T
|
|
||||||
get() = values[state]
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CountryFilter(countries: List<Country>) : EnhancedSelect<Country>(
|
|
||||||
"Nacionalidade",
|
|
||||||
countries.toTypedArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class Status(val name: String, val id: Int) {
|
|
||||||
override fun toString(): String = name
|
|
||||||
}
|
|
||||||
|
|
||||||
private class StatusFilter(statuses: List<Status>) : EnhancedSelect<Status>(
|
|
||||||
"Status",
|
|
||||||
statuses.toTypedArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class SortProperty(val name: String, val slug: String) {
|
|
||||||
override fun toString(): String = name
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SortByFilter(val sortProperties: List<SortProperty>) : Filter.Sort(
|
|
||||||
"Ordenar por",
|
|
||||||
sortProperties.map { it.name }.toTypedArray(),
|
|
||||||
Selection(2, ascending = false)
|
|
||||||
)
|
|
||||||
|
|
||||||
// fetch('https://api.saikai.com.br/api/genres')
|
// fetch('https://api.saikai.com.br/api/genres')
|
||||||
// .then(res => res.json())
|
// .then(res => res.json())
|
||||||
// .then(res => console.log(res.data.map(g => `Genre("${g.name}", ${g.id})`).join(',\n')))
|
// .then(res => console.log(res.data.map(g => `Genre("${g.name}", ${g.id})`).join(',\n')))
|
||||||
|
@ -382,12 +343,13 @@ class SaikaiScan : HttpSource() {
|
||||||
GenreFilter(getGenreList())
|
GenreFilter(getGenreList())
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun String.toDate(): Long {
|
private inline fun <reified T> Response.parseAs(): T = use {
|
||||||
return try {
|
json.decodeFromString(it.body?.string().orEmpty())
|
||||||
DATE_FORMATTER.parse(this)?.time ?: 0L
|
|
||||||
} catch (e: ParseException) {
|
|
||||||
0L
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toDate(): Long {
|
||||||
|
return runCatching { DATE_FORMATTER.parse(this)?.time }
|
||||||
|
.getOrNull() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toStatus(): Int = when (this) {
|
private fun String.toStatus(): Int = when (this) {
|
||||||
|
@ -403,7 +365,9 @@ class SaikaiScan : HttpSource() {
|
||||||
private const val COMIC_FORMAT_ID = "2"
|
private const val COMIC_FORMAT_ID = "2"
|
||||||
private const val PER_PAGE = "12"
|
private const val PER_PAGE = "12"
|
||||||
|
|
||||||
private val DATE_FORMATTER = SimpleDateFormat("yyyy-MM-dd", Locale("pt", "BR"))
|
private val DATE_FORMATTER by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale("pt", "BR"))
|
||||||
|
}
|
||||||
|
|
||||||
private const val API_URL = "https://api.saikai.com.br"
|
private const val API_URL = "https://api.saikai.com.br"
|
||||||
private const val IMAGE_SERVER_URL = "https://s3-alpha.saikai.com.br"
|
private const val IMAGE_SERVER_URL = "https://s3-alpha.saikai.com.br"
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.pt.saikaiscan
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
|
class Genre(title: String, val id: Int) : Filter.CheckBox(title)
|
||||||
|
|
||||||
|
class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres)
|
||||||
|
|
||||||
|
data class Country(val name: String, val id: Int) {
|
||||||
|
override fun toString(): String = name
|
||||||
|
}
|
||||||
|
|
||||||
|
open class EnhancedSelect<T>(name: String, values: Array<T>) : Filter.Select<T>(name, values) {
|
||||||
|
val selected: T
|
||||||
|
get() = values[state]
|
||||||
|
}
|
||||||
|
|
||||||
|
class CountryFilter(countries: List<Country>) : EnhancedSelect<Country>(
|
||||||
|
"Nacionalidade",
|
||||||
|
countries.toTypedArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Status(val name: String, val id: Int) {
|
||||||
|
override fun toString(): String = name
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusFilter(statuses: List<Status>) : EnhancedSelect<Status>(
|
||||||
|
"Status",
|
||||||
|
statuses.toTypedArray()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SortProperty(val name: String, val slug: String) {
|
||||||
|
override fun toString(): String = name
|
||||||
|
}
|
||||||
|
|
||||||
|
class SortByFilter(val sortProperties: List<SortProperty>) : Filter.Sort(
|
||||||
|
"Ordenar por",
|
||||||
|
sortProperties.map { it.name }.toTypedArray(),
|
||||||
|
Selection(2, ascending = false)
|
||||||
|
)
|
Loading…
Reference in New Issue