Fix PerfScan : Complete rewrite for new site and API (#9310)
* Refactor PerfScan extension: update base URL, remove unused theme package, and implement new API response models * Fix Review * Fix consistency on URL
This commit is contained in:
parent
8a9231c5af
commit
68df5d3b69
@ -1,9 +1,8 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Perf Scan'
|
extName = 'Perf Scan'
|
||||||
extClass = '.PerfScan'
|
extClass = '.PerfScan'
|
||||||
themePkg = 'heancms'
|
baseUrl = 'https://perf-scan.net'
|
||||||
baseUrl = 'https://perf-scan.fr'
|
extVersionCode = 30
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,24 +1,157 @@
|
|||||||
package eu.kanade.tachiyomi.extension.fr.perfscan
|
package eu.kanade.tachiyomi.extension.fr.perfscan
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
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 keiyoushi.utils.parseAs
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class PerfScan : HeanCms("Perf Scan", "https://perf-scan.fr", "fr") {
|
class PerfScan : HttpSource() {
|
||||||
|
override val name = "Perf Scan"
|
||||||
|
override val baseUrl = "https://perf-scan.net"
|
||||||
|
private val apiUrl = "https://api.perf-scan.net"
|
||||||
|
override val lang = "fr"
|
||||||
|
override val supportsLatest = true
|
||||||
|
override val versionId = 2
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
.rateLimitHost(apiUrl.toHttpUrl(), 1, 2)
|
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
||||||
|
private val chapterNumberFormat = DecimalFormat("#.##")
|
||||||
|
|
||||||
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
|
.add("Referer", "$baseUrl/")
|
||||||
|
.add("Origin", baseUrl)
|
||||||
|
|
||||||
|
// ============================== Popular ===============================
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val url = "$apiUrl/series".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("ranking", "POPULAR")
|
||||||
|
.addQueryParameter("rankingType", "YEARLY")
|
||||||
|
.addQueryParameter("type", "COMIC")
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("take", "24")
|
||||||
.build()
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
preferences.run {
|
val result = response.parseAs<PerfScanResponse<List<PerfScanSeries>>>()
|
||||||
if (contains("pref_url_map")) {
|
val mangaList = result.data.map { series ->
|
||||||
edit().remove("pref_url_map").apply()
|
SManga.create().apply {
|
||||||
|
url = "/series/${series.slug}"
|
||||||
|
title = series.title
|
||||||
|
thumbnail_url = "$apiUrl/cdn/${series.thumbnail}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasNextPage = result.data.size == 24
|
||||||
|
return MangasPage(mangaList, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Latest ===============================
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val url = "$apiUrl/series".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("type", "COMIC")
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("take", "24")
|
||||||
|
.addQueryParameter("latestUpdate", "true")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
// =============================== Search ===============================
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val url = "$apiUrl/series".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("type", "COMIC")
|
||||||
|
.addQueryParameter("title", query)
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("take", "24")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
|
return baseUrl + manga.url
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== Manga Details ============================
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
val slug = manga.url.substringAfterLast("/")
|
||||||
|
return GET("$apiUrl/series/$slug", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val result = response.parseAs<PerfScanResponse<PerfScanSeriesDetails>>()
|
||||||
|
val series = result.data
|
||||||
|
return SManga.create().apply {
|
||||||
|
url = "/series/${series.slug}"
|
||||||
|
title = series.title
|
||||||
|
author = series.author
|
||||||
|
artist = series.artist
|
||||||
|
description = series.description
|
||||||
|
status = parseStatus(series.statusObject?.name)
|
||||||
|
genre = series.seriesGenre.joinToString { it.genre.name }
|
||||||
|
thumbnail_url = "$apiUrl/cdn/${series.thumbnail}"
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStatus(status: String?): Int {
|
||||||
|
return when (status?.lowercase()) {
|
||||||
|
"en cours" -> SManga.ONGOING
|
||||||
|
"terminé" -> SManga.COMPLETED
|
||||||
|
"en pause" -> SManga.ON_HIATUS
|
||||||
|
"annulé" -> SManga.CANCELLED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Chapters ==============================
|
||||||
|
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val result = response.parseAs<PerfScanResponse<PerfScanSeriesDetails>>()
|
||||||
|
val seriesSlug = result.data.slug
|
||||||
|
return result.data.chapters
|
||||||
|
.sortedByDescending { it.index }
|
||||||
|
.map { chapter ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
val chapterIndex = chapterNumberFormat.format(chapter.index)
|
||||||
|
url = "/series/$seriesSlug/chapter/$chapterIndex"
|
||||||
|
name = "Chapitre $chapterIndex" + if (!chapter.title.isNullOrEmpty() && chapter.title != "-") " - ${chapter.title}" else ""
|
||||||
|
date_upload = dateFormat.tryParse(chapter.createdAt)
|
||||||
|
scanlator = chapter.season.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val useNewQueryEndpoint = true
|
// =============================== Pages ================================
|
||||||
override val useNewChapterEndpoint = true
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
return GET(apiUrl + chapter.url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val result = response.parseAs<PerfScanResponse<PerfScanPageList>>()
|
||||||
|
return result.data.content.mapIndexed { index, image ->
|
||||||
|
Page(index, imageUrl = "$apiUrl/cdn/${image.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.fr.perfscan
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanResponse<T>(
|
||||||
|
val data: T,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanSeries(
|
||||||
|
val slug: String,
|
||||||
|
val title: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanSeriesDetails(
|
||||||
|
val title: String,
|
||||||
|
val slug: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val description: String? = null,
|
||||||
|
val author: String? = null,
|
||||||
|
val artist: String? = null,
|
||||||
|
@SerialName("SeriesGenre") val seriesGenre: List<PerfScanGenreObject> = emptyList(),
|
||||||
|
@SerialName("Status") val statusObject: PerfScanStatusObject? = null,
|
||||||
|
@SerialName("Chapter") val chapters: List<PerfScanChapter> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanGenreObject(
|
||||||
|
@SerialName("Genre") val genre: PerfScanGenre,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanGenre(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanStatusObject(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanChapter(
|
||||||
|
val id: String,
|
||||||
|
val index: Float,
|
||||||
|
val title: String?,
|
||||||
|
val createdAt: String,
|
||||||
|
@SerialName("Season") val season: PerfScanSeason,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanSeason(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanPageList(
|
||||||
|
val content: List<PerfScanImage>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PerfScanImage(
|
||||||
|
val value: String,
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user