Update Spyfakku (#3712)
* Delete Dto * Update SpyFakku * Remove some non-null assert, * Wrap date parsing around try catch * test * Use API * Remove unused property * Update * Update SpyFakku.kt
This commit is contained in:
parent
73984b1dcf
commit
592645fa9d
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'SpyFakku'
|
extName = 'SpyFakku'
|
||||||
extClass = '.SpyFakku'
|
extClass = '.SpyFakku'
|
||||||
extVersionCode = 1
|
extVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@ fun getFilters(): FilterList {
|
||||||
TextFilter("Tags", "tag"),
|
TextFilter("Tags", "tag"),
|
||||||
TextFilter("Artists", "artist"),
|
TextFilter("Artists", "artist"),
|
||||||
TextFilter("Magazines", "magazine"),
|
TextFilter("Magazines", "magazine"),
|
||||||
|
TextFilter("Publishers", "publisher"),
|
||||||
TextFilter("Parodies", "parody"),
|
TextFilter("Parodies", "parody"),
|
||||||
TextFilter("Circles", "circle"),
|
TextFilter("Circles", "circle"),
|
||||||
TextFilter("Pages", "pages"),
|
TextFilter("Events", "event"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +27,9 @@ internal open class SortFilter(name: String, selection: Selection, private val v
|
||||||
}
|
}
|
||||||
|
|
||||||
private val getSortsList: List<Pair<String, String>> = listOf(
|
private val getSortsList: List<Pair<String, String>> = listOf(
|
||||||
Pair("ID", "id"),
|
|
||||||
Pair("Title", "title"),
|
Pair("Title", "title"),
|
||||||
Pair("Created", "created_at"),
|
Pair("Relevance", "relevance"),
|
||||||
Pair("Published", "published_at"),
|
Pair("Date Added", "created_at"),
|
||||||
|
Pair("Date Released", "released_at"),
|
||||||
Pair("Pages", "pages"),
|
Pair("Pages", "pages"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,27 +9,26 @@ 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.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SpyFakku : HttpSource() {
|
class SpyFakku : HttpSource() {
|
||||||
|
|
||||||
override val name = "SpyFakku"
|
override val name = "SpyFakku"
|
||||||
|
|
||||||
override val baseUrl = "https://spy.fakku.cc"
|
override val baseUrl = "https://fakku.cc"
|
||||||
|
|
||||||
private val baseImageUrl = "https://cdn.fakku.cc/data"
|
private val baseImageUrl = "https://cdn.fakku.cc/image"
|
||||||
|
|
||||||
|
private val baseApiUrl = "$baseUrl/api"
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
|
@ -42,34 +41,32 @@ class SpyFakku : HttpSource() {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.set("referer", "$baseUrl/")
|
.set("Referer", "$baseUrl/")
|
||||||
.set("origin", baseUrl)
|
.set("Origin", baseUrl)
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET(baseUrl, headers)
|
return GET("$baseApiUrl/library?sort=released_at&page=$page", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val document = response.asJsoup()
|
val library = response.parseAs<HentaiLib>()
|
||||||
|
|
||||||
val mangas = document.select("article.entry").map(::popularMangaFromElement)
|
val mangas = library.archives.map(::popularManga)
|
||||||
|
|
||||||
val hasNextPage = document.selectFirst(".next") != null
|
val hasNextPage = library.archives.isNotEmpty()
|
||||||
|
|
||||||
return MangasPage(mangas, hasNextPage)
|
return MangasPage(mangas, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
private fun popularManga(hentai: ShortHentai) = SManga.create().apply {
|
||||||
with(element.selectFirst("a")!!) {
|
setUrlWithoutDomain("$baseUrl/g/${hentai.id}")
|
||||||
setUrlWithoutDomain(absUrl("href"))
|
title = hentai.title
|
||||||
title = attr("title")
|
thumbnail_url = "$baseImageUrl/${hentai.hash}/cover"
|
||||||
}
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = baseUrl.toHttpUrl().newBuilder().apply {
|
val url = "$baseApiUrl/library".toHttpUrl().newBuilder().apply {
|
||||||
val terms = mutableListOf(query.trim())
|
val terms = mutableListOf(query.trim())
|
||||||
|
|
||||||
filters.forEach { filter ->
|
filters.forEach { filter ->
|
||||||
|
@ -83,7 +80,7 @@ class SpyFakku : HttpSource() {
|
||||||
if (filter.state.isNotEmpty()) {
|
if (filter.state.isNotEmpty()) {
|
||||||
terms += filter.state.split(",").filter { it.isNotBlank() }.map { tag ->
|
terms += filter.state.split(",").filter { it.isNotBlank() }.map { tag ->
|
||||||
val trimmed = tag.trim().replace(" ", "_")
|
val trimmed = tag.trim().replace(" ", "_")
|
||||||
(if (trimmed.startsWith("-")) "-" else "") + filter.type + "&:" + trimmed.removePrefix("-")
|
(if (trimmed.startsWith("-")) "-" else "") + filter.type + ":" + trimmed.removePrefix("-")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +88,6 @@ class SpyFakku : HttpSource() {
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addPathSegment("search")
|
|
||||||
addQueryParameter("q", terms.joinToString(" "))
|
addQueryParameter("q", terms.joinToString(" "))
|
||||||
addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
@ -99,62 +95,94 @@ class SpyFakku : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
return GET(baseUrl + manga.url + ".json", headers)
|
manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" }
|
||||||
|
return GET(baseApiUrl + manga.url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
return GET(baseUrl + chapter.url + ".json", headers)
|
chapter.url = Regex("^/archive/(\\d+)/.*").replace(chapter.url) { "/g/${it.groupValues[1]}" }
|
||||||
|
return GET(baseApiUrl + chapter.url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList() = getFilters()
|
override fun getFilterList() = getFilters()
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH)
|
private val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH)
|
||||||
|
private val releasedAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
private val createdAtFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply {
|
||||||
|
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 = "/archive/$id/$slug"
|
url = "/g/$id"
|
||||||
author = artists?.joinToString { it.value }
|
author = (circles?.emptyToNull() ?: artists)?.joinToString { it.value }
|
||||||
artist = artists?.joinToString { it.value }
|
artist = artists?.joinToString { it.value }
|
||||||
genre = tags?.joinToString { it.value }
|
genre = tags?.joinToString { it.value }
|
||||||
thumbnail_url = "$baseImageUrl/$id/1/288.webp"
|
thumbnail_url = "$baseImageUrl/$hash/cover"
|
||||||
description = buildString {
|
description = buildString {
|
||||||
circle?.joinToString { it.value }?.let {
|
this@toSManga.description?.let {
|
||||||
|
append(this@toSManga.description, "\n\n")
|
||||||
|
}
|
||||||
|
circles?.emptyToNull()?.joinToString { it.value }?.let {
|
||||||
append("Circles: ", it, "\n")
|
append("Circles: ", it, "\n")
|
||||||
}
|
}
|
||||||
magazines?.joinToString { it.value }?.let {
|
publishers?.emptyToNull()?.joinToString { it.value }?.let {
|
||||||
|
append("Publishers: ", it, "\n")
|
||||||
|
}
|
||||||
|
magazines?.emptyToNull()?.joinToString { it.value }?.let {
|
||||||
append("Magazines: ", it, "\n")
|
append("Magazines: ", it, "\n")
|
||||||
}
|
}
|
||||||
parodies?.joinToString { it.value }?.let {
|
events?.emptyToNull()?.joinToString { it.value }?.let {
|
||||||
|
append("Events: ", it, "\n\n")
|
||||||
|
}
|
||||||
|
parodies?.emptyToNull()?.joinToString { it.value }?.let {
|
||||||
append("Parodies: ", it, "\n")
|
append("Parodies: ", it, "\n")
|
||||||
}
|
}
|
||||||
append(
|
append("Pages: ", pages, "\n\n")
|
||||||
"Created At: ",
|
|
||||||
dateFormat.format(
|
try {
|
||||||
Date(createdAt * 1000),
|
releasedAtFormat.parse(released_at)?.let {
|
||||||
),
|
append("Released: ", dateReformat.format(it.time), "\n")
|
||||||
"\n",
|
}
|
||||||
)
|
} catch (_: Exception) {}
|
||||||
append("Pages: ", pages, "\n")
|
|
||||||
|
try {
|
||||||
|
createdAtFormat.parse(created_at)?.let {
|
||||||
|
append("Added: ", dateReformat.format(it.time), "\n")
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
status = SManga.COMPLETED
|
status = SManga.COMPLETED
|
||||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga = runBlocking {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
response.parseAs<Hentai>().toSManga()
|
return response.parseAs<Hentai>().toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Collection<T>.emptyToNull(): Collection<T>? {
|
||||||
|
return this.ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
||||||
|
|
||||||
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 = response.parseAs<Hentai>()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
url = "/archive/${hentai.id}/${hentai.slug}"
|
url = "/g/${hentai.id}"
|
||||||
date_upload = hentai.createdAt * 1000
|
date_upload = try {
|
||||||
|
releasedAtFormat.parse(hentai.released_at)!!.time
|
||||||
|
} catch (e: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -163,19 +191,16 @@ class SpyFakku : HttpSource() {
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val hentai = response.parseAs<Hentai>()
|
val hentai = response.parseAs<Hentai>()
|
||||||
val range = 1..hentai.pages
|
val images = hentai.images
|
||||||
val baseImageUrl = "$baseImageUrl/${hentai.id}/"
|
return images.mapIndexed { index, it ->
|
||||||
return range.map {
|
Page(index, imageUrl = "$baseImageUrl/${hentai.hash}/${it.filename}")
|
||||||
val imageUrl = baseImageUrl + it
|
|
||||||
Page(it - 1, imageUrl = imageUrl)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,40 @@ package eu.kanade.tachiyomi.extension.en.spyfakku
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class HentaiLib(
|
||||||
|
val archives: List<ShortHentai>,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Hentai(
|
class Hentai(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val slug: String,
|
val hash: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val createdAt: Long,
|
val description: String?,
|
||||||
|
val released_at: String,
|
||||||
|
val created_at: String,
|
||||||
val pages: Int,
|
val pages: Int,
|
||||||
|
val publishers: List<Name>?,
|
||||||
val artists: List<Name>?,
|
val artists: List<Name>?,
|
||||||
val circle: List<Name>?,
|
val circles: List<Name>?,
|
||||||
val magazines: List<Name>?,
|
val magazines: List<Name>?,
|
||||||
val parodies: List<Name>?,
|
val parodies: List<Name>?,
|
||||||
|
val events: List<Name>?,
|
||||||
val tags: List<Name>?,
|
val tags: List<Name>?,
|
||||||
|
val images: List<Image>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ShortHentai(
|
||||||
|
val id: Int,
|
||||||
|
val hash: String,
|
||||||
|
val title: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Image(
|
||||||
|
val filename: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
Loading…
Reference in New Issue