195 lines
8.3 KiB
Kotlin
195 lines
8.3 KiB
Kotlin
package eu.kanade.tachiyomi.extension.id.komikcast
|
|
|
|
import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
|
|
import eu.kanade.tachiyomi.network.GET
|
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
import eu.kanade.tachiyomi.source.model.Filter
|
|
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 kotlinx.serialization.decodeFromString
|
|
import kotlinx.serialization.json.Json
|
|
import kotlinx.serialization.json.jsonArray
|
|
import kotlinx.serialization.json.jsonObject
|
|
import okhttp3.Headers
|
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
import okhttp3.OkHttpClient
|
|
import okhttp3.Request
|
|
import org.jsoup.Jsoup
|
|
import org.jsoup.nodes.Document
|
|
import org.jsoup.nodes.Element
|
|
import uy.kohesive.injekt.injectLazy
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
class KomikCast : WPMangaStream("Komik Cast", "https://komikcast.me", "id") {
|
|
// Formerly "Komik Cast (WP Manga Stream)"
|
|
override val id = 972717448578983812
|
|
|
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
|
.connectTimeout(10, TimeUnit.SECONDS)
|
|
.readTimeout(30, TimeUnit.SECONDS)
|
|
.rateLimit(3)
|
|
.build()
|
|
|
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
|
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
|
|
.add("Accept-language", "en-US,en;q=0.9,id;q=0.8")
|
|
.add("Referer", baseUrl)
|
|
.add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0")
|
|
|
|
override fun imageRequest(page: Page): Request {
|
|
val newHeaders = headersBuilder()
|
|
.set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
|
|
.set("Referer", baseUrl)
|
|
.build()
|
|
|
|
return GET(page.imageUrl!!, newHeaders)
|
|
}
|
|
override fun popularMangaSelector() = "div.list-update_item"
|
|
|
|
override fun popularMangaRequest(page: Int): Request {
|
|
return GET("$baseUrl/daftar-komik/page/$page/?orderby=popular", headers)
|
|
}
|
|
|
|
override fun latestUpdatesRequest(page: Int): Request {
|
|
return GET("$baseUrl/komik/page/$page/", headers)
|
|
}
|
|
|
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
val url = if (query.isNotBlank()) {
|
|
val url = "$baseUrl/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
|
val pattern = "\\s+".toRegex()
|
|
val q = query.replace(pattern, "+")
|
|
if (query.isNotEmpty()) {
|
|
url.addQueryParameter("s", q)
|
|
} else {
|
|
url.addQueryParameter("s", "")
|
|
}
|
|
url.toString()
|
|
} else {
|
|
var url = "$baseUrl/daftar-komik/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
|
var orderBy: String
|
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
|
when (filter) {
|
|
is StatusFilter -> url.addQueryParameter("status", arrayOf("", "ongoing", "completed")[filter.state])
|
|
is GenreListFilter -> {
|
|
val genreInclude = mutableListOf<String>()
|
|
filter.state.forEach {
|
|
if (it.state == 1) {
|
|
genreInclude.add(it.id)
|
|
}
|
|
}
|
|
if (genreInclude.isNotEmpty()) {
|
|
genreInclude.forEach { genre ->
|
|
url.addQueryParameter("genre[]", genre)
|
|
}
|
|
}
|
|
}
|
|
is SortByFilter -> {
|
|
orderBy = filter.toUriPart()
|
|
url.addQueryParameter("orderby", orderBy)
|
|
}
|
|
is ProjectFilter -> {
|
|
if (filter.toUriPart() == "project-filter-on") {
|
|
url = "$baseUrl/project-list/page/$page".toHttpUrlOrNull()!!.newBuilder()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
url.toString()
|
|
}
|
|
return GET(url, headers)
|
|
}
|
|
|
|
override fun popularMangaFromElement(element: Element): SManga {
|
|
val manga = SManga.create()
|
|
element.select("a").first().let {
|
|
manga.setUrlWithoutDomain(it.attr("href"))
|
|
manga.title = it.select(".list-update_item-info h3.title").text()
|
|
manga.thumbnail_url = element.select("div.list-update_item-image img").imgAttr()
|
|
}
|
|
return manga
|
|
}
|
|
|
|
override fun mangaDetailsParse(document: Document): SManga {
|
|
return SManga.create().apply {
|
|
document.select("div.komik_info").firstOrNull()?.let { infoElement ->
|
|
genre = infoElement.select(".komik_info-content-genre a").joinToString { it.text() }
|
|
status = parseStatus(infoElement.select("span:contains(Status:)").firstOrNull()?.ownText())
|
|
author = infoElement.select("span:contains(Author:)").firstOrNull()?.ownText()
|
|
artist = infoElement.select("span:contains(Author:)").firstOrNull()?.ownText()
|
|
description = infoElement.select("div.komik_info-description-sinopsis p").joinToString("\n") { it.text() }
|
|
thumbnail_url = infoElement.select("div.komik_info-content-thumbnail img").imgAttr()
|
|
|
|
// add series type(manga/manhwa/manhua/other) thinggy to genre
|
|
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
|
|
if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
|
|
genre += if (genre!!.isEmpty()) it else ", $it"
|
|
}
|
|
}
|
|
|
|
// add alternative name to manga description
|
|
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
|
|
if (it.isBlank().not() && it != "N/A" && it != "-") {
|
|
description = when {
|
|
description.isNullOrBlank() -> altName + it
|
|
else -> description + "\n\n$altName" + it
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override val seriesTypeSelector = "span:contains(Type) a"
|
|
override val altNameSelector = ".komik_info-content-native"
|
|
|
|
override fun chapterListSelector() = "div.komik_info-chapters li"
|
|
|
|
override fun chapterFromElement(element: Element): SChapter {
|
|
val urlElement = element.select("a").first()
|
|
val chapter = SChapter.create()
|
|
chapter.setUrlWithoutDomain(urlElement.attr("href"))
|
|
chapter.name = urlElement.text()
|
|
chapter.date_upload = element.select(".chapter-link-time").firstOrNull()?.text()?.let { parseChapterDate(it) } ?: 0
|
|
return chapter
|
|
}
|
|
|
|
override fun pageListParse(document: Document): List<Page> {
|
|
var doc = document
|
|
var cssQuery = "div#chapter_body .main-reading-area img.size-full"
|
|
val imageListRegex = Regex("chapterImages = (.*) \\|\\|")
|
|
val imageListMatchResult = imageListRegex.find(document.toString())
|
|
|
|
if (imageListMatchResult != null) {
|
|
val imageListJson = imageListMatchResult.destructured.toList()[0]
|
|
val imageList = json.parseToJsonElement(imageListJson).jsonObject
|
|
|
|
var imageServer = "cdn"
|
|
if (!imageList.containsKey(imageServer)) imageServer = imageList.keys.first()
|
|
val imageElement = imageList[imageServer]!!.jsonArray.joinToString("")
|
|
doc = Jsoup.parse(json.decodeFromString(imageElement))
|
|
cssQuery = "img.size-full"
|
|
}
|
|
|
|
return doc.select(cssQuery)
|
|
.mapIndexed { i, img -> Page(i, "", img.attr("abs:Src")) }
|
|
}
|
|
|
|
override fun getFilterList() = FilterList(
|
|
Filter.Header("NOTE: Ignored if using text search!"),
|
|
Filter.Separator(),
|
|
SortByFilter(),
|
|
Filter.Separator(),
|
|
StatusFilter(),
|
|
Filter.Separator(),
|
|
GenreListFilter(getGenreList()),
|
|
Filter.Header("NOTE: cant be used with other filter!"),
|
|
Filter.Header("$name Project List page"),
|
|
ProjectFilter()
|
|
)
|
|
|
|
private val json: Json by injectLazy()
|
|
}
|