Comickiba: change name & factory source (#19514)

* Add manhuagold

* remove comickiba

* dont keep old id
This commit is contained in:
Secozzi 2024-01-01 21:03:07 +00:00 committed by GitHub
parent 40c354f4d0
commit 66019f2511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 386 additions and 9 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

View File

@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -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)
}
}

View File

@ -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"),
)
}
}

View File

@ -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"),

View File

@ -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

View File

@ -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 {