Comickiba: change name & factory source (#19514)
* Add manhuagold * remove comickiba * dont keep old id
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 90 KiB |
|
@ -1,7 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.comickiba
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class ComicKiba : Madara("ComicKiba", "https://comickiba.com", "en") {
|
||||
override val pageListParseSelector = "li.blocks-gallery-item img:nth-child(1), div.reading-content p > img, .read-container .reading-content img"
|
||||
}
|
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 106 KiB |
|
@ -0,0 +1,233 @@
|
|||
package eu.kanade.tachiyomi.extension.en.comickiba
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangareader.MangaReader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
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.util.asJsoup
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.TextNode
|
||||
import org.jsoup.select.Evaluator
|
||||
import rx.Observable
|
||||
|
||||
class Manhuagold : MangaReader() {
|
||||
|
||||
override val name = "Manhuagold"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val baseUrl = "https://manhuagold.com"
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
// Popular
|
||||
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
GET("$baseUrl/filter/$page/?sort=views&sex=All&chapter_count=0", headers)
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
GET("$baseUrl/filter/$page/?sort=latest-updated&sex=All&chapter_count=0", headers)
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val urlBuilder = baseUrl.toHttpUrl().newBuilder()
|
||||
if (query.isNotBlank()) {
|
||||
urlBuilder.addPathSegment("search").apply {
|
||||
addQueryParameter("keyword", query)
|
||||
}
|
||||
} else {
|
||||
urlBuilder.addPathSegment("filter").apply {
|
||||
filters.ifEmpty(::getFilterList).forEach { filter ->
|
||||
when (filter) {
|
||||
is Select -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
is GenresFilter -> {
|
||||
addQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
urlBuilder.addPathSegment(page.toString())
|
||||
urlBuilder.addPathSegment("")
|
||||
|
||||
return GET(urlBuilder.build(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = ".manga_list-sbs .manga-poster"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) =
|
||||
SManga.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
element.selectFirst(Evaluator.Tag("img"))!!.let {
|
||||
title = it.attr("alt")
|
||||
thumbnail_url = it.imgAttr()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = "ul.pagination > li.active + li"
|
||||
|
||||
// Filters
|
||||
|
||||
override fun getFilterList() =
|
||||
FilterList(
|
||||
Note,
|
||||
StatusFilter(),
|
||||
SortFilter(),
|
||||
GenresFilter(),
|
||||
)
|
||||
|
||||
// Details
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val root = document.selectFirst(Evaluator.Id("ani_detail"))!!
|
||||
val mangaTitle = root.selectFirst(Evaluator.Class("manga-name"))!!.ownText()
|
||||
title = mangaTitle
|
||||
description = root.run {
|
||||
val description = selectFirst(Evaluator.Class("description"))!!.ownText()
|
||||
when (val altTitle = selectFirst(Evaluator.Class("manga-name-or"))!!.ownText()) {
|
||||
"", mangaTitle -> description
|
||||
else -> "$description\n\nAlternative Title: $altTitle"
|
||||
}
|
||||
}
|
||||
thumbnail_url = root.selectFirst(Evaluator.Tag("img"))!!.imgAttr()
|
||||
genre = root.selectFirst(Evaluator.Class("genres"))!!.children().joinToString { it.ownText() }
|
||||
for (item in root.selectFirst(Evaluator.Class("anisc-info"))!!.children()) {
|
||||
if (item.hasClass("item").not()) continue
|
||||
when (item.selectFirst(Evaluator.Class("item-head"))!!.ownText()) {
|
||||
"Authors:" -> item.parseAuthorsTo(this)
|
||||
"Status:" -> status = when (item.selectFirst(Evaluator.Class("name"))!!.ownText().lowercase()) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"completed" -> SManga.COMPLETED
|
||||
"on-hold" -> SManga.ON_HIATUS
|
||||
"canceled" -> SManga.CANCELLED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.parseAuthorsTo(manga: SManga) {
|
||||
val authors = select(Evaluator.Tag("a"))
|
||||
val text = authors.map { it.ownText().replace(",", "") }
|
||||
val count = authors.size
|
||||
when (count) {
|
||||
0 -> return
|
||||
1 -> {
|
||||
manga.author = text[0]
|
||||
return
|
||||
}
|
||||
}
|
||||
val authorList = ArrayList<String>(count)
|
||||
val artistList = ArrayList<String>(count)
|
||||
for ((index, author) in authors.withIndex()) {
|
||||
val textNode = author.nextSibling() as? TextNode
|
||||
val list = if (textNode != null && "(Art)" in textNode.wholeText) artistList else authorList
|
||||
list.add(text[index])
|
||||
}
|
||||
if (authorList.isEmpty().not()) manga.author = authorList.joinToString()
|
||||
if (artistList.isEmpty().not()) manga.artist = artistList.joinToString()
|
||||
}
|
||||
|
||||
// Chapters
|
||||
|
||||
override fun chapterListRequest(mangaUrl: String, type: String): Request =
|
||||
GET(baseUrl + mangaUrl, headers)
|
||||
|
||||
override fun parseChapterElements(response: Response, isVolume: Boolean): List<Element> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override val chapterType = ""
|
||||
override val volumeType = ""
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return client.newCall(chapterListRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map(::parseChapterList)
|
||||
}
|
||||
|
||||
private fun parseChapterList(response: Response): List<SChapter> {
|
||||
val document = response.use { it.asJsoup() }
|
||||
|
||||
return document.select(chapterListSelector())
|
||||
.map(::chapterFromElement)
|
||||
}
|
||||
|
||||
private fun chapterListSelector(): String = "#chapters-list > li"
|
||||
|
||||
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
element.selectFirst("a")!!.run {
|
||||
setUrlWithoutDomain(attr("href"))
|
||||
name = selectFirst(".name")?.text() ?: text()
|
||||
}
|
||||
}
|
||||
|
||||
// Images
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.fromCallable {
|
||||
val document = client.newCall(pageListRequest(chapter)).execute().asJsoup()
|
||||
|
||||
val script = document.selectFirst("script:containsData(const CHAPTER_ID)")!!.data()
|
||||
val id = script.substringAfter("const CHAPTER_ID = ").substringBefore(";")
|
||||
|
||||
val ajaxHeaders = super.headersBuilder().apply {
|
||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
||||
add("Referer", baseUrl + chapter.url)
|
||||
add("X-Requested-With", "XMLHttpRequest")
|
||||
}.build()
|
||||
|
||||
val ajaxUrl = "$baseUrl/ajax/image/list/chap/$id"
|
||||
client.newCall(GET(ajaxUrl, ajaxHeaders)).execute().let(::pageListParse)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.use { it.parseHtmlProperty() }
|
||||
|
||||
val pageList = document.select("div").map {
|
||||
val index = it.attr("data-number").toInt()
|
||||
val imgUrl = it.imgAttr().ifEmpty { it.selectFirst("img")!!.imgAttr() }
|
||||
|
||||
Page(index, "", imgUrl)
|
||||
}
|
||||
|
||||
return pageList
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
// From mangathemesia
|
||||
private fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
||||
hasAttr("data-src") -> attr("abs:data-src")
|
||||
else -> attr("abs:src")
|
||||
}
|
||||
|
||||
private fun Response.parseHtmlProperty(): Document {
|
||||
val html = Json.parseToJsonElement(body.string()).jsonObject["html"]!!.jsonPrimitive.content
|
||||
return Jsoup.parseBodyFragment(html)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package eu.kanade.tachiyomi.extension.en.comickiba
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
object Note : Filter.Header("NOTE: Ignored if using text search!")
|
||||
|
||||
sealed class Select(
|
||||
name: String,
|
||||
val param: String,
|
||||
values: Array<String>,
|
||||
) : Filter.Select<String>(name, values) {
|
||||
open val selection: String
|
||||
get() = if (state == 0) "" else state.toString()
|
||||
}
|
||||
|
||||
class StatusFilter(
|
||||
values: Array<String> = statuses.keys.toTypedArray(),
|
||||
) : Select("Status", "status", values) {
|
||||
override val selection: String
|
||||
get() = statuses[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val statuses = mapOf(
|
||||
"All" to "",
|
||||
"Completed" to "completed",
|
||||
"OnGoing" to "on-going",
|
||||
"On-Hold" to "on-hold",
|
||||
"Canceled" to "canceled",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SortFilter(
|
||||
values: Array<String> = orders.keys.toTypedArray(),
|
||||
) : Select("Sort", "sort", values) {
|
||||
override val selection: String
|
||||
get() = orders[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val orders = mapOf(
|
||||
"Default" to "default",
|
||||
"Latest Updated" to "latest-updated",
|
||||
"Most Viewed" to "views",
|
||||
"Most Viewed Month" to "views_month",
|
||||
"Most Viewed Week" to "views_week",
|
||||
"Most Viewed Day" to "views_day",
|
||||
"Score" to "score",
|
||||
"Name A-Z" to "az",
|
||||
"Name Z-A" to "za",
|
||||
"The highest chapter count" to "chapters",
|
||||
"Newest" to "new",
|
||||
"Oldest" to "old",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
||||
|
||||
class GenresFilter(
|
||||
values: List<Genre> = genres,
|
||||
) : Filter.Group<Genre>("Genres", values) {
|
||||
val param = "genres"
|
||||
|
||||
val selection: String
|
||||
get() = state.filter { it.state }.joinToString(",") { it.id }
|
||||
|
||||
companion object {
|
||||
private val genres: List<Genre>
|
||||
get() = listOf(
|
||||
Genre("Action", "37"),
|
||||
Genre("Adaptation", "19"),
|
||||
Genre("Adult", "5310"),
|
||||
Genre("Adventure", "38"),
|
||||
Genre("Aliens", "5436"),
|
||||
Genre("Animals", "1552"),
|
||||
Genre("Award Winning", "39"),
|
||||
Genre("Comedy", "202"),
|
||||
Genre("Comic", "287"),
|
||||
Genre("Cooking", "277"),
|
||||
Genre("Crime", "2723"),
|
||||
Genre("Delinquents", "4438"),
|
||||
Genre("Demons", "379"),
|
||||
Genre("Drama", "3"),
|
||||
Genre("Ecchi", "17"),
|
||||
Genre("Fantasy", "197"),
|
||||
Genre("Full Color", "13"),
|
||||
Genre("Gender Bender", "221"),
|
||||
Genre("Genderswap", "2290"),
|
||||
Genre("Ghosts", "2866"),
|
||||
Genre("Gore", "42"),
|
||||
Genre("Harem", "222"),
|
||||
Genre("Historical", "4"),
|
||||
Genre("Horror", "5"),
|
||||
Genre("Isekai", "259"),
|
||||
Genre("Josei", "292"),
|
||||
Genre("Loli", "5449"),
|
||||
Genre("Long Strip", "7"),
|
||||
Genre("Magic", "272"),
|
||||
Genre("Manhwa", "266"),
|
||||
Genre("Martial Arts", "40"),
|
||||
Genre("Mature", "5311"),
|
||||
Genre("Mecha", "2830"),
|
||||
Genre("Medical", "1598"),
|
||||
Genre("Military", "43"),
|
||||
Genre("Monster Girls", "2307"),
|
||||
Genre("Monsters", "298"),
|
||||
Genre("Music", "3182"),
|
||||
Genre("Mystery", "6"),
|
||||
Genre("Office Workers", "14"),
|
||||
Genre("Official Colored", "1046"),
|
||||
Genre("Philosophical", "2776"),
|
||||
Genre("Post-Apocalyptic", "1059"),
|
||||
Genre("Psychological", "493"),
|
||||
Genre("Reincarnation", "204"),
|
||||
Genre("Reverse", "280"),
|
||||
Genre("Reverse Harem", "199"),
|
||||
Genre("Romance", "186"),
|
||||
Genre("School Life", "601"),
|
||||
Genre("Sci-Fi", "1845"),
|
||||
Genre("Sexual Violence", "731"),
|
||||
Genre("Shoujo", "254"),
|
||||
Genre("Slice of Life", "10"),
|
||||
Genre("Sports", "4066"),
|
||||
Genre("Superhero", "481"),
|
||||
Genre("Supernatural", "198"),
|
||||
Genre("Survival", "44"),
|
||||
Genre("Thriller", "1058"),
|
||||
Genre("Time Travel", "299"),
|
||||
Genre("Tragedy", "41"),
|
||||
Genre("Video Games", "1846"),
|
||||
Genre("Villainess", "278"),
|
||||
Genre("Virtual Reality", "1847"),
|
||||
Genre("Web Comic", "12"),
|
||||
Genre("Webtoon", "279"),
|
||||
Genre("Webtoons", "267"),
|
||||
Genre("Wuxia", "203"),
|
||||
Genre("Yaoi", "18"),
|
||||
Genre("Yuri", "11"),
|
||||
Genre("Zombies", "1060"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -73,7 +73,6 @@ class MadaraGenerator : ThemeSourceGenerator {
|
|||
SingleLang("CoffeeManga.top (unoriginal)", "https://coffeemanga.top", "en", isNsfw = true, className = "CoffeeMangaTop"),
|
||||
SingleLang("Colored Manga", "https://coloredmanga.com", "en", overrideVersionCode = 2),
|
||||
SingleLang("Comic Scans", "https://www.comicscans.org", "en", isNsfw = false),
|
||||
SingleLang("ComicKiba", "https://comickiba.com", "en", overrideVersionCode = 1),
|
||||
SingleLang("Comics Valley", "https://comicsvalley.com", "hi", isNsfw = true, overrideVersionCode = 1),
|
||||
SingleLang("ComicsWorld", "https://comicsworld.in", "hi"),
|
||||
SingleLang("Comicz.net v2", "https://v2.comiz.net", "all", isNsfw = true, className = "ComiczNetV2"),
|
||||
|
|
|
@ -73,7 +73,7 @@ abstract class MangaReader : HttpSource(), ConfigurableSource {
|
|||
|
||||
open fun updateChapterList(manga: SManga, chapters: List<SChapter>) = Unit
|
||||
|
||||
final override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
|
||||
val path = manga.url
|
||||
val isVolume = path.endsWith(VOLUME_URL_SUFFIX)
|
||||
val type = if (isVolume) volumeType else chapterType
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.tachiyomi.multisrc.mangareader
|
||||
|
||||
import generator.ThemeSourceData.MultiLang
|
||||
import generator.ThemeSourceData.SingleLang
|
||||
import generator.ThemeSourceGenerator
|
||||
|
||||
class MangaReaderGenerator : ThemeSourceGenerator {
|
||||
|
@ -23,6 +24,15 @@ class MangaReaderGenerator : ThemeSourceGenerator {
|
|||
isNsfw = true,
|
||||
overrideVersionCode = 3,
|
||||
),
|
||||
SingleLang(
|
||||
name = "Manhuagold",
|
||||
baseUrl = "https://manhuagold.com",
|
||||
lang = "en",
|
||||
isNsfw = true,
|
||||
className = "Manhuagold",
|
||||
pkgName = "comickiba",
|
||||
overrideVersionCode = 33,
|
||||
),
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|