Fix Spyfakku (#4345)

This commit is contained in:
KenjieDec 2024-08-01 15:03:42 +07:00 committed by Draff
parent 311bea3a8a
commit 869afb9534
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 134 additions and 41 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'SpyFakku' extName = 'SpyFakku'
extClass = '.SpyFakku' extClass = '.SpyFakku'
extVersionCode = 3 extVersionCode = 4
isNsfw = true isNsfw = true
} }

View File

@ -11,6 +11,14 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
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.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -24,11 +32,11 @@ class SpyFakku : HttpSource() {
override val name = "SpyFakku" override val name = "SpyFakku"
override val baseUrl = "https://fakku.cc" override val baseUrl = "https://w1.fakku.cc"
private val baseImageUrl = "https://cdn.fakku.cc/image" private val baseImageUrl = "https://i1.fakku.cc/image"
private val baseApiUrl = "$baseUrl/api" private val baseApiEnd = "/__data.json"
override val lang = "en" override val lang = "en"
@ -45,28 +53,77 @@ class SpyFakku : HttpSource() {
.set("Origin", baseUrl) .set("Origin", baseUrl)
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseApiUrl/library?sort=released_at&page=$page", headers) return GET("$baseUrl$baseApiEnd?sort=released_at&page=$page", headers)
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val library = response.parseAs<HentaiLib>() val library = response.parseAs<HentaiLib>()
val data = getMangasDatas(library.data)
val mangas = library.archives.map(::popularManga) val mangas = data.first.map(::popularManga)
val hasNextPage = library.archives.isNotEmpty() val hasNextPage = data.second
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
private fun getMangasDatas(data: List<JsonElement>): Pair<List<ShortHentai>, Boolean> {
val indexes = json.decodeFromJsonElement<Indexes>(data[0].jsonObject)
val indexesIndexes = json.decodeFromJsonElement<List<Int>>(data[indexes.archives])
val hasNext = (data[indexes.page].jsonPrimitive.int * 24) < data[indexes.total].jsonPrimitive.int
return indexesIndexes.map {
val hentaiIndexes = json.decodeFromJsonElement<ShortHentaiIndexes>(data[it])
val id = data[hentaiIndexes.id].jsonPrimitive.content
val hash = data[hentaiIndexes.hash].jsonPrimitive.content
val title = data[hentaiIndexes.title].jsonPrimitive.content
ShortHentai(id, hash, title)
} to hasNext
}
private fun getManga(data: List<JsonElement>): Hentai {
fun <T> getNameIndex(index: Int?, decode: (JsonElement) -> T): List<T>? {
return index?.let { idx ->
data[idx].jsonArray.map { el -> decode(data[el.jsonPrimitive.int]) }
}
}
fun JsonElement.toName(): String = this.jsonPrimitive.content.split(" ").joinToString(" ") {
it.lowercase().replaceFirstChar { char -> char.titlecase() }
}
val hentaiIndexes = json.decodeFromJsonElement<HentaiIndexes>(data[0])
val id = data[hentaiIndexes.id].jsonPrimitive.int
val hash = data[hentaiIndexes.hash].jsonPrimitive.content
val title = data[hentaiIndexes.title].jsonPrimitive.content
val description = data[hentaiIndexes.description].jsonPrimitive.contentOrNull
val released_at = data[hentaiIndexes.released_at].jsonPrimitive.content
val created_at = data[hentaiIndexes.created_at].jsonPrimitive.content
val pages = data[hentaiIndexes.pages].jsonPrimitive.int
val size = data[hentaiIndexes.size].jsonPrimitive.long
val publishers = getNameIndex(hentaiIndexes.publishers) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val artists = getNameIndex(hentaiIndexes.artists) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val circles = getNameIndex(hentaiIndexes.circles) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val magazines = getNameIndex(hentaiIndexes.magazines) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val parodies = getNameIndex(hentaiIndexes.parodies) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val events = getNameIndex(hentaiIndexes.events) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val tags = getNameIndex(hentaiIndexes.tags) { data[json.decodeFromJsonElement<NameIndex>(it).name].toName() }
val images = getNameIndex(hentaiIndexes.images) { data[json.decodeFromJsonElement<ImageIndex>(it).filename].toName() }!!
return Hentai(id, hash, title, description, released_at, created_at, pages, size, publishers, artists, circles, magazines, parodies, events, tags, images)
}
private fun popularManga(hentai: ShortHentai) = SManga.create().apply { private fun popularManga(hentai: ShortHentai) = SManga.create().apply {
setUrlWithoutDomain("$baseUrl/g/${hentai.id}") setUrlWithoutDomain("$baseUrl/g/${hentai.id}" + baseApiEnd)
title = hentai.title title = hentai.title
thumbnail_url = "$baseImageUrl/${hentai.hash}/1/c" thumbnail_url = "$baseImageUrl/${hentai.hash}/1/c"
} }
override fun searchMangaParse(response: Response) = popularMangaParse(response) override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseApiUrl/library".toHttpUrl().newBuilder().apply { val url = "$baseUrl$baseApiEnd".toHttpUrl().newBuilder().apply {
val terms = mutableListOf(query.trim()) val terms = mutableListOf(query.trim())
filters.forEach { filter -> filters.forEach { filter ->
@ -96,12 +153,12 @@ class SpyFakku : HttpSource() {
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga): Request {
manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" } manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" }
return GET(baseApiUrl + manga.url, headers) return GET(baseUrl + manga.url, headers)
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
chapter.url = Regex("^/archive/(\\d+)/.*").replace(chapter.url) { "/g/${it.groupValues[1]}" } chapter.url = Regex("^/archive/(\\d+)/.*").replace(chapter.url) { "/g/${it.groupValues[1]}" }
return GET(baseApiUrl + chapter.url, headers) return GET(baseUrl + chapter.url, headers)
} }
override fun getFilterList() = getFilters() override fun getFilterList() = getFilters()
@ -110,34 +167,34 @@ class SpyFakku : HttpSource() {
private val releasedAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply { private val releasedAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("UTC") timeZone = TimeZone.getTimeZone("UTC")
} }
private val createdAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply { private val createdAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("UTC") timeZone = TimeZone.getTimeZone("UTC")
} }
private fun Hentai.toSManga() = SManga.create().apply { private fun Hentai.toSManga() = SManga.create().apply {
title = this@toSManga.title title = this@toSManga.title
url = "/g/$id" url = "/g/$id"
author = (circles?.emptyToNull() ?: artists)?.joinToString { it.name } author = (circles?.emptyToNull() ?: artists)?.joinToString()
artist = artists?.joinToString { it.name } artist = artists?.joinToString()
genre = tags?.joinToString { it.name } genre = tags?.joinToString()
thumbnail_url = "$baseImageUrl/$hash/1/c" thumbnail_url = "$baseImageUrl/$hash/1/c"
description = buildString { description = buildString {
this@toSManga.description?.let { this@toSManga.description?.let {
append(this@toSManga.description, "\n\n") append(this@toSManga.description, "\n\n")
} }
circles?.emptyToNull()?.joinToString { it.name }?.let { circles?.emptyToNull()?.joinToString()?.let {
append("Circles: ", it, "\n") append("Circles: ", it, "\n")
} }
publishers?.emptyToNull()?.joinToString { it.name }?.let { publishers?.emptyToNull()?.joinToString()?.let {
append("Publishers: ", it, "\n") append("Publishers: ", it, "\n")
} }
magazines?.emptyToNull()?.joinToString { it.name }?.let { magazines?.emptyToNull()?.joinToString()?.let {
append("Magazines: ", it, "\n") append("Magazines: ", it, "\n")
} }
events?.emptyToNull()?.joinToString { it.name }?.let { events?.emptyToNull()?.joinToString()?.let {
append("Events: ", it, "\n\n") append("Events: ", it, "\n\n")
} }
parodies?.emptyToNull()?.joinToString { it.name }?.let { parodies?.emptyToNull()?.joinToString()?.let {
append("Parodies: ", it, "\n") append("Parodies: ", it, "\n")
} }
append("Pages: ", pages, "\n\n") append("Pages: ", pages, "\n\n")
@ -169,7 +226,7 @@ class SpyFakku : HttpSource() {
} }
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
return response.parseAs<Hentai>().toSManga() return getManga(response.parseAs<HentaiLib>().data).toSManga()
} }
private fun <T> Collection<T>.emptyToNull(): Collection<T>? { private fun <T> Collection<T>.emptyToNull(): Collection<T>? {
@ -181,12 +238,12 @@ class SpyFakku : HttpSource() {
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val hentai = response.parseAs<Hentai>() val hentai = getManga(response.parseAs<HentaiLib>().data)
return listOf( return listOf(
SChapter.create().apply { SChapter.create().apply {
name = "Chapter" name = "Chapter"
url = "/g/${hentai.id}" url = "/g/${hentai.id}" + baseApiEnd
date_upload = try { date_upload = try {
releasedAtFormat.parse(hentai.released_at)!!.time releasedAtFormat.parse(hentai.released_at)!!.time
} catch (e: Exception) { } catch (e: Exception) {
@ -199,14 +256,14 @@ class SpyFakku : HttpSource() {
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val hentai = response.parseAs<Hentai>() val hentai = getManga(response.parseAs<HentaiLib>().data)
val images = hentai.images val images = hentai.images
return images.mapIndexed { index, it -> return images.mapIndexed { index, it ->
Page(index, imageUrl = "$baseImageUrl/${hentai.hash}/${it.filename}") Page(index, imageUrl = "$baseImageUrl/${hentai.hash}/$it")
} }
} }
private inline fun <reified T> Response.parseAs(): T { private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string()) return json.decodeFromString(body.string().substringAfter("\n"))
} }
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()

View File

@ -1,10 +1,46 @@
package eu.kanade.tachiyomi.extension.en.spyfakku package eu.kanade.tachiyomi.extension.en.spyfakku
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@Serializable @Serializable
class HentaiLib( class HentaiLib(
val archives: List<ShortHentai>, val data: List<JsonElement>,
)
@Serializable
class Indexes(
val archives: Int,
val page: Int,
val limit: Int,
val total: Int,
)
@Serializable
class ShortHentaiIndexes(
val id: Int,
val hash: Int,
val title: Int,
)
@Serializable
class HentaiIndexes(
val id: Int,
val hash: Int,
val title: Int,
val description: Int,
val released_at: Int,
val created_at: Int,
val pages: Int,
val size: Int,
val publishers: Int?,
val artists: Int?,
val circles: Int?,
val magazines: Int?,
val parodies: Int?,
val events: Int?,
val tags: Int?,
val images: Int,
) )
@Serializable @Serializable
@ -16,30 +52,30 @@ class Hentai(
val released_at: String, val released_at: String,
val created_at: String, val created_at: String,
val pages: Int, val pages: Int,
val size: Int = 0, val size: Long = 0L,
val publishers: List<Name>?, val publishers: List<String>?,
val artists: List<Name>?, val artists: List<String>?,
val circles: List<Name>?, val circles: List<String>?,
val magazines: List<Name>?, val magazines: List<String>?,
val parodies: List<Name>?, val parodies: List<String>?,
val events: List<Name>?, val events: List<String>?,
val tags: List<Name>?, val tags: List<String>?,
val images: List<Image>, val images: List<String>,
) )
@Serializable @Serializable
class ShortHentai( class ShortHentai(
val id: Int, val id: String,
val hash: String, val hash: String,
val title: String, val title: String,
) )
@Serializable @Serializable
class Image( class ImageIndex(
val filename: String, val filename: Int,
) )
@Serializable @Serializable
class Name( class NameIndex(
val name: String, val name: Int,
) )