Refactor the code of some extensions. (#10313)

This commit is contained in:
Alessandro Jean 2022-01-04 07:44:45 -03:00 committed by GitHub
parent 5cb8df48af
commit 418cded260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 147 additions and 142 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'Argos Scan'
pkgNameSuffix = 'pt.argosscan'
extClass = '.ArgosScan'
extVersionCode = 18
extVersionCode = 19
}
dependencies {

View File

@ -16,7 +16,9 @@ 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 kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.add
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonArray
@ -83,14 +85,13 @@ class ArgosScan : HttpSource(), ConfigurableSource {
}
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)
}
val projectList = result["data"]!!.jsonObject["getProjects"]!!
.let { json.decodeFromJsonElement<ArgosProjectListDto>(it) }
val projectList = result.data["getProjects"]!!
val mangaList = projectList.projects
.map(::genericMangaFromObject)
@ -155,36 +156,32 @@ class ArgosScan : HttpSource(), ConfigurableSource {
}
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)
}
val project = result["data"]!!.jsonObject["project"]!!.jsonObject
.let { json.decodeFromJsonElement<ArgosProjectDto>(it) }
val project = result.data["project"]!!
title = project.name!!
thumbnail_url = "$baseUrl/images/${project.id}/${project.cover!!}"
description = project.description.orEmpty()
author = project.authors.orEmpty().joinToString(", ")
author = project.authors.orEmpty().joinToString()
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 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)
}
val project = result["data"]!!.jsonObject["project"]!!.jsonObject
.let { json.decodeFromJsonElement<ArgosProjectDto>(it) }
return project.chapters.map(::chapterFromObject)
return result.data["project"]!!.chapters.map(::chapterFromObject)
}
private fun chapterFromObject(chapter: ArgosChapterDto): SChapter = SChapter.create().apply {
@ -211,7 +208,7 @@ class ArgosScan : HttpSource(), ConfigurableSource {
}
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) {
throw Exception(REQUEST_ERROR)
@ -345,6 +342,10 @@ class ArgosScan : HttpSource(), ConfigurableSource {
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 {
return runCatching { DATE_PARSER.parse(this)?.time }
.getOrNull() ?: 0L

View File

@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.extension.pt.argosscan
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ArgosResponseDto<T>(
val data: Map<String, T>? = null
)
@Serializable
data class ArgosProjectListDto(
val count: Int = 0,

View File

@ -6,7 +6,7 @@ ext {
extName = 'HipercooL'
pkgNameSuffix = 'pt.hipercool'
extClass = '.Hipercool'
extVersionCode = 8
extVersionCode = 9
isNsfw = true
}

View File

@ -1,6 +1,6 @@
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.POST
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.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
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.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class Hipercool : HttpSource() {
@ -43,7 +41,8 @@ class Hipercool : HttpSource() {
override val supportsLatest = true
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()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
@ -54,14 +53,14 @@ class Hipercool : HttpSource() {
private val json: Json by injectLazy()
private fun genericMangaListParse(response: Response): MangasPage {
val chapters = json.decodeFromString<List<HipercoolChapterDto>>(response.body!!.string())
val chapters = response.parseAs<List<HipercoolChapterDto>>()
if (chapters.isEmpty())
return MangasPage(emptyList(), false)
val mangaList = chapters
.distinctBy { it.book!!.title }
.map(::genericMangaFromObject)
.distinctBy { it.title }
val hasNextPage = chapters.size == DEFAULT_COUNT
@ -87,17 +86,14 @@ class Hipercool : HttpSource() {
override fun latestUpdatesParse(response: Response): MangasPage = genericMangaListParse(response)
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 json = buildJsonObject {
put("start", (page - 1) * DEFAULT_COUNT)
put("content", DEFAULT_COUNT)
put("text", query)
put("type", "text")
}
val body = json.toString().toRequestBody(mediaType)
val body = json.encodeToString(searchPayload).toRequestBody(JSON_MEDIA_TYPE)
return POST("$baseUrl/api/books/chapters/search", headers, body)
}
@ -119,39 +115,22 @@ class Hipercool : HttpSource() {
return GET("$baseUrl/api/books/$slug", headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val book = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
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
thumbnail_url = book.slug.toThumbnailUrl(book.revision)
description = book.synopsis.orEmpty()
artist = artists
author = authors
genre = tags
}
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 = json.decodeFromString<HipercoolBookDto>(response.body!!.string())
val book = response.parseAs<HipercoolBookDto>()
if (book.chapters is JsonPrimitive)
return emptyList()
@ -166,8 +145,9 @@ class Hipercool : HttpSource() {
name = "Cap. " + chapter.title
chapter_number = chapter.title.toFloatOrNull() ?: -1f
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(chapter.slug)
.addQueryParameter("images", chapter.images.toString())
@ -183,30 +163,26 @@ class Hipercool : HttpSource() {
val bookSlug = chapterUrl.pathSegments[1]
val chapterSlug = chapterUrl.pathSegments[2]
val images = chapterUrl.queryParameter("images")!!.toInt()
val revision = chapterUrl.queryParameter("revision")!!.toInt()
val revision = chapterUrl.queryParameter("revision")!!
val pages = arrayListOf<Page>()
// Create the pages.
for (i in 1..images) {
val imageUrl = "$STATIC_URL/books".toHttpUrlOrNull()!!.newBuilder()
val pages = List(images) { i ->
val imageUrl = "$STATIC_URL/books".toHttpUrl().newBuilder()
.addPathSegment(bookSlug)
.addPathSegment(chapterSlug)
.addPathSegment("$bookSlug-chapter-$chapterSlug-page-$i.jpg")
.addQueryParameter("revision", revision.toString())
.addPathSegment("$bookSlug-chapter-$chapterSlug-page-${i + 1}.jpg")
.addQueryParameter("revision", revision)
.toString()
pages += Page(i - 1, chapter.url, imageUrl)
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 pageListParse(response: Response): List<Page> =
throw Exception("This method should not be called!")
override fun fetchImageUrl(page: Page): Observable<String> {
return Observable.just(page.imageUrl!!)
}
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
override fun imageUrlParse(response: Response): String = ""
@ -218,12 +194,13 @@ class Hipercool : HttpSource() {
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 try {
DATE_FORMATTER.parse(substringBefore("T"))?.time ?: 0L
} catch (e: ParseException) {
0L
}
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
private fun String.toThumbnailUrl(revision: Int): String =
@ -236,11 +213,15 @@ class Hipercool : HttpSource() {
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/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 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)
}
}
}

View File

@ -12,12 +12,18 @@ data class HipercoolBookDto(
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 values: List<HipercoolTagDto> = emptyList(),
val slug: String
)
@Serializable
@ -28,3 +34,11 @@ data class HipercoolChapterDto(
val slug: String,
val title: String
)
@Serializable
data class HipercoolSearchDto(
val start: Int,
val count: Int,
val text: String,
val type: String
)

View File

@ -6,7 +6,7 @@ ext {
extName = 'Saikai Scan'
pkgNameSuffix = 'pt.saikaiscan'
extClass = '.SaikaiScan'
extVersionCode = 7
extVersionCode = 8
}
dependencies {

View File

@ -1,9 +1,8 @@
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.asObservableSuccess
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
@ -20,10 +19,8 @@ import okhttp3.Response
import org.jsoup.Jsoup
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class SaikaiScan : HttpSource() {
@ -36,7 +33,8 @@ class SaikaiScan : HttpSource() {
override val supportsLatest = true
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()
private val json: Json by injectLazy()
@ -63,7 +61,7 @@ class SaikaiScan : HttpSource() {
}
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 hasNextPage = result.meta!!.currentPage < result.meta.lastPage
@ -170,7 +168,7 @@ class SaikaiScan : HttpSource() {
}
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]
title = story.title
@ -202,13 +200,13 @@ class SaikaiScan : HttpSource() {
}
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]
return story.releases
.filter { it.isActive == 1 }
.map { chapterFromObject(it, story.slug) }
.sortedByDescending { it.chapter_number }
.sortedByDescending(SChapter::chapter_number)
}
private fun chapterFromObject(obj: SaikaiScanReleaseDto, storySlug: String): SChapter =
@ -216,7 +214,7 @@ class SaikaiScan : HttpSource() {
name = "Capítulo ${obj.chapter}" +
(if (obj.title.isNullOrEmpty().not()) " - ${obj.title}" else "")
chapter_number = obj.chapter.toFloatOrNull() ?: -1f
date_upload = obj.publishedAt.substringBefore(" ").toDate()
date_upload = obj.publishedAt.toDate()
scanlator = this@SaikaiScan.name
url = "/ler/comics/$storySlug/${obj.id}/${obj.slug}"
}
@ -238,7 +236,7 @@ class SaikaiScan : HttpSource() {
}
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 ->
Page(i, "", "$IMAGE_SERVER_URL/${obj.image}")
@ -257,43 +255,6 @@ class SaikaiScan : HttpSource() {
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')
// .then(res => res.json())
// .then(res => console.log(res.data.map(g => `Genre("${g.name}", ${g.id})`).join(',\n')))
@ -382,12 +343,13 @@ class SaikaiScan : HttpSource() {
GenreFilter(getGenreList())
)
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body?.string().orEmpty())
}
private fun String.toDate(): Long {
return try {
DATE_FORMATTER.parse(this)?.time ?: 0L
} catch (e: ParseException) {
0L
}
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
private fun String.toStatus(): Int = when (this) {
@ -403,7 +365,9 @@ class SaikaiScan : HttpSource() {
private const val COMIC_FORMAT_ID = "2"
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 IMAGE_SERVER_URL = "https://s3-alpha.saikai.com.br"

View File

@ -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)
)