2022-08-16 11:24:35 +00:00
|
|
|
package eu.kanade.tachiyomi.extension.id.komikcast
|
|
|
|
|
|
|
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
|
|
import eu.kanade.tachiyomi.network.GET
|
|
|
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
2022-09-11 12:55:11 +00:00
|
|
|
import eu.kanade.tachiyomi.source.model.Filter
|
|
|
|
import eu.kanade.tachiyomi.source.model.FilterList
|
2022-08-16 11:24:35 +00:00
|
|
|
import eu.kanade.tachiyomi.source.model.Page
|
2022-09-14 16:11:38 +00:00
|
|
|
import eu.kanade.tachiyomi.source.model.SChapter
|
2023-10-03 20:37:52 +00:00
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
2022-08-16 11:24:35 +00:00
|
|
|
import kotlinx.serialization.json.jsonArray
|
|
|
|
import kotlinx.serialization.json.jsonObject
|
|
|
|
import okhttp3.Headers
|
2022-09-11 12:55:11 +00:00
|
|
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
2022-08-16 11:24:35 +00:00
|
|
|
import okhttp3.OkHttpClient
|
|
|
|
import okhttp3.Request
|
|
|
|
import org.jsoup.Jsoup
|
|
|
|
import org.jsoup.nodes.Document
|
|
|
|
import org.jsoup.nodes.Element
|
2022-09-14 16:11:38 +00:00
|
|
|
import java.util.Calendar
|
2023-10-03 20:37:52 +00:00
|
|
|
import java.util.Locale
|
|
|
|
|
2023-12-02 15:42:18 +00:00
|
|
|
class KomikCast : MangaThemesia("Komik Cast", "https://komikcast.lol", "id", "/daftar-komik") {
|
2022-08-16 11:24:35 +00:00
|
|
|
|
|
|
|
// Formerly "Komik Cast (WP Manga Stream)"
|
|
|
|
override val id = 972717448578983812
|
|
|
|
|
2023-07-15 22:52:35 +00:00
|
|
|
override val client: OkHttpClient = super.client.newBuilder()
|
2022-08-16 11:24:35 +00:00
|
|
|
.rateLimit(3)
|
|
|
|
.build()
|
|
|
|
|
2023-07-15 22:52:35 +00:00
|
|
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
2022-08-16 11:24:35 +00:00
|
|
|
.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")
|
|
|
|
|
|
|
|
override fun imageRequest(page: Page): Request {
|
|
|
|
val newHeaders = headersBuilder()
|
|
|
|
.set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
|
2024-01-24 19:05:08 +00:00
|
|
|
.set("Referer", "$baseUrl/")
|
2022-08-16 11:24:35 +00:00
|
|
|
.build()
|
|
|
|
|
|
|
|
return GET(page.imageUrl!!, newHeaders)
|
|
|
|
}
|
|
|
|
|
2022-09-01 21:55:04 +00:00
|
|
|
override fun popularMangaRequest(page: Int) = customPageRequest(page, "orderby", "popular")
|
|
|
|
override fun latestUpdatesRequest(page: Int) = customPageRequest(page, "sortby", "update")
|
|
|
|
|
|
|
|
private fun customPageRequest(page: Int, filterKey: String, filterValue: String): Request {
|
|
|
|
val pagePath = if (page > 1) "page/$page/" else ""
|
|
|
|
|
|
|
|
return GET("$baseUrl$mangaUrlDirectory/$pagePath?$filterKey=$filterValue", headers)
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:24:35 +00:00
|
|
|
override fun searchMangaSelector() = "div.list-update_item"
|
|
|
|
|
|
|
|
override fun searchMangaFromElement(element: Element) = super.searchMangaFromElement(element).apply {
|
2023-02-12 03:22:32 +00:00
|
|
|
title = element.selectFirst("h3.title")!!.ownText()
|
2022-08-16 11:24:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override val seriesDetailsSelector = "div.komik_info:has(.komik_info-content)"
|
|
|
|
override val seriesTitleSelector = "h1.komik_info-content-body-title"
|
|
|
|
override val seriesDescriptionSelector = ".komik_info-description-sinopsis"
|
|
|
|
override val seriesAltNameSelector = ".komik_info-content-native"
|
|
|
|
override val seriesGenreSelector = ".komik_info-content-genre a"
|
|
|
|
override val seriesThumbnailSelector = ".komik_info-content-thumbnail img"
|
2022-11-15 14:12:37 +00:00
|
|
|
override val seriesStatusSelector = ".komik_info-content-info:contains(Status)"
|
2022-08-16 11:24:35 +00:00
|
|
|
|
2023-10-03 20:37:52 +00:00
|
|
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
|
|
|
document.selectFirst(seriesDetailsSelector)?.let { seriesDetails ->
|
|
|
|
title = seriesDetails.selectFirst(seriesTitleSelector)?.text()
|
|
|
|
?.replace("bahasa indonesia", "", ignoreCase = true)?.trim().orEmpty()
|
|
|
|
artist = seriesDetails.selectFirst(seriesArtistSelector)?.ownText().removeEmptyPlaceholder()
|
|
|
|
author = seriesDetails.selectFirst(seriesAuthorSelector)?.ownText().removeEmptyPlaceholder()
|
|
|
|
description = seriesDetails.select(seriesDescriptionSelector).joinToString("\n") { it.text() }.trim()
|
|
|
|
// Add alternative name to manga description
|
|
|
|
val altName = seriesDetails.selectFirst(seriesAltNameSelector)?.ownText().takeIf { it.isNullOrBlank().not() }
|
|
|
|
altName?.let {
|
|
|
|
description = "$description\n\n$altNamePrefix$altName".trim()
|
|
|
|
}
|
|
|
|
val genres = seriesDetails.select(seriesGenreSelector).map { it.text() }.toMutableList()
|
|
|
|
// Add series type (manga/manhwa/manhua/other) to genre
|
|
|
|
seriesDetails.selectFirst(seriesTypeSelector)?.ownText().takeIf { it.isNullOrBlank().not() }?.let { genres.add(it) }
|
|
|
|
genre = genres.map { genre ->
|
|
|
|
genre.lowercase(Locale.forLanguageTag(lang)).replaceFirstChar { char ->
|
|
|
|
if (char.isLowerCase()) {
|
|
|
|
char.titlecase(Locale.forLanguageTag(lang))
|
|
|
|
} else {
|
|
|
|
char.toString()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.joinToString { it.trim() }
|
|
|
|
|
|
|
|
status = seriesDetails.selectFirst(seriesStatusSelector)?.text().parseStatus()
|
|
|
|
thumbnail_url = seriesDetails.select(seriesThumbnailSelector).imgAttr()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:24:35 +00:00
|
|
|
override fun chapterListSelector() = "div.komik_info-chapters li"
|
|
|
|
|
2022-09-14 16:11:38 +00:00
|
|
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
|
|
val urlElements = element.select("a")
|
|
|
|
setUrlWithoutDomain(urlElements.attr("href"))
|
2023-09-06 16:38:55 +00:00
|
|
|
name = element.select(".chapter-link-item").text()
|
2022-09-14 16:11:38 +00:00
|
|
|
date_upload = parseChapterDate2(element.select(".chapter-link-time").text())
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun parseChapterDate2(date: String): Long {
|
|
|
|
return if (date.endsWith("ago")) {
|
|
|
|
val value = date.split(' ')[0].toInt()
|
|
|
|
when {
|
|
|
|
"min" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.MINUTE, value * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
"hour" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.HOUR_OF_DAY, value * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
"day" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.DATE, value * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
"week" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.DATE, value * 7 * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
"month" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.MONTH, value * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
"year" in date -> Calendar.getInstance().apply {
|
|
|
|
add(Calendar.YEAR, value * -1)
|
|
|
|
}.timeInMillis
|
|
|
|
else -> {
|
|
|
|
0L
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
dateFormat.parse(date)?.time ?: 0
|
|
|
|
} catch (_: Exception) {
|
|
|
|
0L
|
|
|
|
}
|
|
|
|
}
|
2022-08-16 11:24:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun pageListParse(document: Document): List<Page> {
|
|
|
|
var doc = document
|
2022-10-03 11:47:45 +00:00
|
|
|
var cssQuery = "div#chapter_body .main-reading-area img"
|
2022-08-16 11:24:35 +00:00
|
|
|
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("")
|
2023-02-12 03:22:32 +00:00
|
|
|
doc = Jsoup.parse(imageElement)
|
2022-08-16 11:24:35 +00:00
|
|
|
cssQuery = "img.size-full"
|
|
|
|
}
|
|
|
|
|
|
|
|
return doc.select(cssQuery)
|
2023-10-13 17:13:19 +00:00
|
|
|
.mapIndexed { i, img -> Page(i, document.location(), img.imgAttr()) }
|
2022-08-16 11:24:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override val hasProjectPage: Boolean = true
|
2022-09-11 12:55:11 +00:00
|
|
|
override val projectPageString = "/project-list"
|
|
|
|
|
|
|
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
|
|
val url = baseUrl.toHttpUrl().newBuilder()
|
|
|
|
|
|
|
|
if (query.isNotEmpty()) {
|
|
|
|
url.addPathSegments("page/$page/").addQueryParameter("s", query)
|
|
|
|
} else {
|
|
|
|
url.addPathSegment(mangaUrlDirectory.substring(1)).addPathSegments("page/$page/")
|
|
|
|
}
|
|
|
|
|
|
|
|
filters.forEach { filter ->
|
|
|
|
when (filter) {
|
|
|
|
is StatusFilter -> {
|
|
|
|
url.addQueryParameter("status", filter.selectedValue())
|
|
|
|
}
|
|
|
|
is TypeFilter -> {
|
|
|
|
url.addQueryParameter("type", filter.selectedValue())
|
|
|
|
}
|
|
|
|
is OrderByFilter -> {
|
|
|
|
url.addQueryParameter("orderby", filter.selectedValue())
|
|
|
|
}
|
|
|
|
is GenreListFilter -> {
|
|
|
|
filter.state
|
|
|
|
.filter { it.state != Filter.TriState.STATE_IGNORE }
|
|
|
|
.forEach {
|
|
|
|
val value = if (it.state == Filter.TriState.STATE_EXCLUDE) "-${it.value}" else it.value
|
|
|
|
url.addQueryParameter("genre[]", value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if site has project page, default value "hasProjectPage" = false
|
|
|
|
is ProjectFilter -> {
|
|
|
|
if (filter.selectedValue() == "project-filter-on") {
|
|
|
|
url.setPathSegment(0, projectPageString.substring(1))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else -> { /* Do Nothing */ }
|
|
|
|
}
|
|
|
|
}
|
2024-01-12 15:56:53 +00:00
|
|
|
return GET(url.build())
|
2022-09-11 12:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private class StatusFilter : SelectFilter(
|
|
|
|
"Status",
|
|
|
|
arrayOf(
|
|
|
|
Pair("All", ""),
|
|
|
|
Pair("Ongoing", "ongoing"),
|
2023-02-11 19:21:03 +00:00
|
|
|
Pair("Completed", "completed"),
|
|
|
|
),
|
2022-09-11 12:55:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
private class TypeFilter : SelectFilter(
|
|
|
|
"Type",
|
|
|
|
arrayOf(
|
|
|
|
Pair("All", ""),
|
|
|
|
Pair("Manga", "manga"),
|
|
|
|
Pair("Manhwa", "manhwa"),
|
2023-02-11 19:21:03 +00:00
|
|
|
Pair("Manhua", "manhua"),
|
|
|
|
),
|
2022-09-11 12:55:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
private class OrderByFilter(defaultOrder: String? = null) : SelectFilter(
|
|
|
|
"Sort By",
|
|
|
|
arrayOf(
|
|
|
|
Pair("Default", ""),
|
|
|
|
Pair("A-Z", "titleasc"),
|
|
|
|
Pair("Z-A", "titledesc"),
|
|
|
|
Pair("Update", "update"),
|
2023-02-11 19:21:03 +00:00
|
|
|
Pair("Popular", "popular"),
|
2022-09-11 12:55:11 +00:00
|
|
|
),
|
2023-02-11 19:21:03 +00:00
|
|
|
defaultOrder,
|
2022-09-11 12:55:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
override fun getFilterList(): FilterList {
|
|
|
|
val filters = mutableListOf<Filter<*>>(
|
|
|
|
Filter.Separator(),
|
|
|
|
StatusFilter(),
|
|
|
|
TypeFilter(),
|
|
|
|
OrderByFilter(),
|
|
|
|
Filter.Header("Genre exclusion is not available for all sources"),
|
|
|
|
GenreListFilter(getGenreList()),
|
2023-10-03 20:37:52 +00:00
|
|
|
Filter.Separator(),
|
|
|
|
Filter.Header("NOTE: Can't be used with other filter!"),
|
|
|
|
Filter.Header("$name Project List page"),
|
|
|
|
ProjectFilter(),
|
2022-09-11 12:55:11 +00:00
|
|
|
)
|
|
|
|
return FilterList(filters)
|
|
|
|
}
|
2022-08-16 11:24:35 +00:00
|
|
|
}
|