Add 18Kami (#4262)
* Add 18Kami * Apply suggestions - Apply vetleledaal's suggestions * Apply suggestion - Apply bapeey's suggestion Co-authored-by: bapeey <90949336+bapeey@users.noreply.github.com> * Lint Fix --------- Co-authored-by: bapeey <90949336+bapeey@users.noreply.github.com>
This commit is contained in:
parent
10ddb3734f
commit
a5e5ccceec
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = '18Kami'
|
||||||
|
extClass = '.Kami18'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,62 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.kami18
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
|
fun getFilters(): FilterList {
|
||||||
|
return FilterList(
|
||||||
|
Filter.Header("Filter is ignored when using text search"),
|
||||||
|
SortFilter("Sort", getSortsList),
|
||||||
|
TimelineFilter("Timeline", getTimelinesList),
|
||||||
|
TypeFilter("Type", getTypes),
|
||||||
|
Filter.Separator(),
|
||||||
|
TextFilter("Tags"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Filters **/
|
||||||
|
|
||||||
|
internal open class TextFilter(name: String) : Filter.Text(name)
|
||||||
|
|
||||||
|
internal class SortFilter(name: String, sortList: List<Pair<String, String>>, state: Int = 0) :
|
||||||
|
SelectFilter(name, sortList, state)
|
||||||
|
|
||||||
|
internal class TypeFilter(name: String, sortList: List<Pair<String, String>>, state: Int = 0) :
|
||||||
|
SelectFilter(name, sortList, state)
|
||||||
|
|
||||||
|
internal class TimelineFilter(name: String, sortList: List<Pair<String, String>>, state: Int = 0) :
|
||||||
|
SelectFilter(name, sortList, state)
|
||||||
|
|
||||||
|
internal open class CheckBoxFilter(name: String, val value: String = "") : Filter.CheckBox(name)
|
||||||
|
|
||||||
|
internal open class SelectFilter(name: String, private val vals: List<Pair<String, String>>, state: Int = 0) :
|
||||||
|
Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
|
||||||
|
fun getValue() = vals[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
private val getTimelinesList: List<Pair<String, String>> = listOf(
|
||||||
|
Pair("All Time", "a"),
|
||||||
|
Pair("Added Today", "d"),
|
||||||
|
Pair("Added This Week", "w"),
|
||||||
|
Pair("Added This Month", "m"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val getTypes: List<Pair<String, String>> = listOf(
|
||||||
|
Pair("All", ""),
|
||||||
|
Pair("Other", "another"),
|
||||||
|
Pair("Comic", "comic"),
|
||||||
|
Pair("Cosplay", "cosplay"),
|
||||||
|
Pair("Image", "image"),
|
||||||
|
Pair("Manga", "manga"),
|
||||||
|
Pair("Manhwa", "manhwa"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val getSortsList: List<Pair<String, String>> = listOf(
|
||||||
|
Pair("Relevant", "mm"),
|
||||||
|
Pair("Most Recent", "mr"),
|
||||||
|
Pair("Most Viewed", "mv"),
|
||||||
|
Pair("Most Photos", "mp"),
|
||||||
|
Pair("Top Rated", "tr"),
|
||||||
|
Pair("Most Commented", "md"),
|
||||||
|
Pair("Most Liked", "tf"),
|
||||||
|
)
|
|
@ -0,0 +1,164 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.kami18
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
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.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class Kami18() : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "18Kami"
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val baseUrl = "https://18kami.com"
|
||||||
|
|
||||||
|
private val baseImageUrl = "$baseUrl/media/photos"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client = network.cloudflareClient
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder().apply {
|
||||||
|
add("Referer", "$baseUrl/")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/albums?o=mv&page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val entries = document.select(".image-container")
|
||||||
|
val hasNextPage = document.selectFirst(".prevnext") != null
|
||||||
|
|
||||||
|
return MangasPage(entries.map(::popularMangaFromElement), hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.selectFirst("a:has(button)")!!.absUrl("href"))
|
||||||
|
title = element.selectFirst("img")!!.attr("title")
|
||||||
|
thumbnail_url = element.selectFirst("img")?.let { img ->
|
||||||
|
img.absUrl("src").takeIf { !it.contains("blank") } ?: img.absUrl("data-original")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/albums?o=mr&page=$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
val url = "$baseUrl/search/photos".toHttpUrl().newBuilder().apply {
|
||||||
|
addQueryParameter("main_tag", "5")
|
||||||
|
addQueryParameter("search_query", query)
|
||||||
|
}.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
var type = ""
|
||||||
|
var search = false
|
||||||
|
filters.forEach {
|
||||||
|
when (it) {
|
||||||
|
is TypeFilter -> {
|
||||||
|
type = it.getValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
is SortFilter -> {
|
||||||
|
addQueryParameter("o", it.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
is TimelineFilter -> {
|
||||||
|
addQueryParameter("t", it.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
is TextFilter -> {
|
||||||
|
if (it.state.isNotBlank()) {
|
||||||
|
search = true
|
||||||
|
addQueryParameter("main_tag", "3")
|
||||||
|
addQueryParameter("search_query", it.state.replace(",", " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addPathSegments(if (search) "search/photos" else "albums")
|
||||||
|
if (type.isNotEmpty()) addPathSegment(type)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun getFilterList() = getFilters()
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
description = buildString {
|
||||||
|
val desc = document.selectFirst("div[class*=p-t-5]:contains(description:)")?.ownText()?.substringAfter(":") ?: ""
|
||||||
|
append(desc)
|
||||||
|
append("\n\n", document.select("div[class\$=p-b-5]:contains(Pages)").text())
|
||||||
|
}
|
||||||
|
status = SManga.UNKNOWN
|
||||||
|
author = document.select("div[class*=p-t-5]:contains(Author) > div").eachText().joinToString()
|
||||||
|
genre = document.select("div[class*=p-t-5]:contains(Tags) > div:not(:contains(add))").eachText().joinToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val doc = response.asJsoup()
|
||||||
|
return doc.selectFirst(".episode")?.let {
|
||||||
|
it.select("ul > a").reversed().mapIndexed { index, element ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain("/photo/" + element.attr("data-album"))
|
||||||
|
name = "Chapter $index"
|
||||||
|
date_upload = try {
|
||||||
|
dateFormat.parse(element.selectFirst("span")!!.text())!!.time
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: listOf(
|
||||||
|
SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain("/photo/" + doc.selectFirst("[id=album_id]")!!.attr("value"))
|
||||||
|
name = "Chapter 1"
|
||||||
|
date_upload = try {
|
||||||
|
dateFormat.parse(doc.selectFirst("[itemprop=datePublished]")!!.text().substringAfter(": "))!!.time
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val contents = document.select("[id*=pageselect] > option")
|
||||||
|
|
||||||
|
val id = response.request.url.toString().filter { it.isDigit() }
|
||||||
|
return contents.mapIndexed { idx, image ->
|
||||||
|
val filename = image.attr("data-page")
|
||||||
|
Page(idx, imageUrl = "$baseImageUrl/$id/$filename")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
}
|
Loading…
Reference in New Issue