Ikiru: rewrite for new site (#10249)
This commit is contained in:
parent
c796e33925
commit
8fe8ca4fd1
@ -1,9 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Ikiru'
|
extName = 'Ikiru'
|
||||||
extClass = '.Ikiru'
|
extClass = '.Ikiru'
|
||||||
themePkg = 'mangathemesia'
|
extVersionCode = 39
|
||||||
baseUrl = 'https://id.ikiru.wtf'
|
|
||||||
overrideVersionCode = 8
|
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,42 +1,176 @@
|
|||||||
package eu.kanade.tachiyomi.extension.id.mangatale
|
package eu.kanade.tachiyomi.extension.id.mangatale
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
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.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class Ikiru : MangaThemesia("Ikiru", "https://id.ikiru.wtf", "id") {
|
class Ikiru : ParsedHttpSource() {
|
||||||
|
// Formerly "MangaTale"
|
||||||
override val id = 1532456597012176985
|
override val id = 1532456597012176985
|
||||||
|
|
||||||
|
override val name = "Ikiru"
|
||||||
|
override val baseUrl = "https://01.ikiru.wtf"
|
||||||
|
override val lang = "id"
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
.rateLimit(12, 3)
|
.rateLimit(12, 3)
|
||||||
.addInterceptor { chain ->
|
|
||||||
val response = chain.proceed(chain.request())
|
|
||||||
val mime = response.headers["Content-Type"]
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
if (mime != "application/octet-stream") {
|
|
||||||
return@addInterceptor response
|
|
||||||
}
|
|
||||||
// Fix image content type
|
|
||||||
val type = IMG_CONTENT_TYPE.toMediaType()
|
|
||||||
val body = response.body.bytes().toResponseBody(type)
|
|
||||||
return@addInterceptor response.newBuilder().body(body)
|
|
||||||
.header("Content-Type", IMG_CONTENT_TYPE).build()
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override val seriesTitleSelector = ".ts-breadcrumb span:last-child span"
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply {
|
// Popular
|
||||||
thumbnail_url = document.selectFirst(seriesThumbnailSelector)?.imgAttr()
|
override fun popularMangaRequest(page: Int): Request = searchMangaRequest(page, "", FilterList())
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = searchMangaSelector()
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/latest-update/?the_page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = "#search-results > div:not(.col-span-full)"
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector() = "#search-results ~ div.col-span-full a:has(svg):last-of-type"
|
||||||
|
|
||||||
|
// Search
|
||||||
|
private var searchNonce: String? = null
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
// TODO: Filter
|
||||||
|
|
||||||
|
if (searchNonce.isNullOrEmpty()) {
|
||||||
|
val document = client.newCall(
|
||||||
|
GET("$baseUrl/ajax-call?type=search_form&action=get_nonce", headers),
|
||||||
|
).execute().asJsoup()
|
||||||
|
searchNonce = document.selectFirst("input[name=search_nonce]")!!.attr("value")
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestBody: RequestBody = MultipartBody.Builder()
|
||||||
|
.setType(MultipartBody.FORM)
|
||||||
|
.addFormDataPart("query", query)
|
||||||
|
.addFormDataPart("page", "$page")
|
||||||
|
.addFormDataPart("nonce", searchNonce!!)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/ajax-call?action=advanced_search", body = requestBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = "div.overflow-hidden:has(a.font-medium)"
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.selectFirst("a")?.absUrl("href") ?: "")
|
||||||
|
thumbnail_url = element.selectFirst("img")?.absUrl("src") ?: ""
|
||||||
|
title = element.selectFirst("a.font-medium")?.text() ?: ""
|
||||||
|
status = parseStatus(element.selectFirst("div span ~ p")?.text() ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = "div button:has(svg)"
|
||||||
|
|
||||||
|
// Manga Details
|
||||||
|
private fun Element.getMangaId() = selectFirst("#gallery-list")?.attr("hx-get")
|
||||||
|
?.substringAfter("manga_id=")?.substringBefore("&")
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
document.selectFirst("article > section").let { element ->
|
||||||
|
return SManga.create().apply {
|
||||||
|
thumbnail_url = element!!.selectFirst(".contents img")?.absUrl("src") ?: ""
|
||||||
|
title = element.selectFirst("h1.font-bold")?.text() ?: ""
|
||||||
|
// TODO: prevent status value from browse change back to default
|
||||||
|
|
||||||
|
val altNames = element.selectFirst("h1 ~ .line-clamp-1")?.text() ?: ""
|
||||||
|
val synopsis = element.selectFirst("#tabpanel-description div[data-show='false']")?.text() ?: ""
|
||||||
|
description = buildString {
|
||||||
|
append(synopsis)
|
||||||
|
if (altNames.isNotEmpty()) {
|
||||||
|
append("\n\nAlternative Title: ", altNames)
|
||||||
|
}
|
||||||
|
document.getMangaId()?.also {
|
||||||
|
append("\n\nID: ", it) // for fetching chapter list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genre = element.select(".space-y-2 div:has(img) p, #tabpanel-description .flex-wrap span").joinToString { it.text() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
|
||||||
|
val mangaId = manga.description
|
||||||
|
?.substringAfterLast("ID: ", "")
|
||||||
|
?.takeIf { it.toIntOrNull() != null }
|
||||||
|
?: client.newCall(mangaDetailsRequest(manga)).execute().asJsoup().getMangaId()
|
||||||
|
?: throw Exception("Could not find manga ID")
|
||||||
|
|
||||||
|
val chapterListUrl = "$baseUrl/ajax-call".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("manga_id", mangaId)
|
||||||
|
.addQueryParameter("page", "") // keep empty for loading hidden chapter
|
||||||
|
.addQueryParameter("action", "chapter_list")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val response = client.newCall(GET(chapterListUrl, headers)).execute()
|
||||||
|
|
||||||
|
response.asJsoup().select("#chapter-list .cursor-pointer a").asReversed().map { element ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
|
name = element.select("span").text()
|
||||||
|
date_upload = dateFormat.tryParse(element.select("time").attr("datetime"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun chapterListSelector() = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
return document.select("main .relative section > img").mapIndexed { i, element ->
|
||||||
|
Page(i, imageUrl = element.attr("abs:src"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Others
|
||||||
|
private fun parseStatus(element: String?): Int {
|
||||||
|
if (element.isNullOrEmpty()) {
|
||||||
|
return SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
return when (element.lowercase()) {
|
||||||
|
"ongoing" -> SManga.ONGOING
|
||||||
|
"completed" -> SManga.COMPLETED
|
||||||
|
"on hiatus" -> SManga.ON_HIATUS
|
||||||
|
"canceled" -> SManga.CANCELLED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val IMG_CONTENT_TYPE = "image/jpeg"
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user