add rawotaku (#1111)

This commit is contained in:
Secozzi 2024-02-08 16:08:09 +00:00 committed by GitHub
parent fd045fccc6
commit b9f753261e
8 changed files with 367 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,251 @@
package eu.kanade.tachiyomi.extension.ja.rawotaku
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.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 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
import java.net.URLEncoder
class RawOtaku : MangaReader() {
override val name = "Raw Otaku"
override val lang = "ja"
override val baseUrl = "https://rawotaku.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/?type=all&status=all&language=all&sort=most-viewed&p=$page", headers)
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) =
GET("$baseUrl/filter/?type=all&status=all&language=all&sort=latest-updated&p=$page", headers)
// =============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (query.isNotBlank()) {
addQueryParameter("q", query)
} else {
addPathSegment("filter")
addPathSegment("")
filters.ifEmpty(::getFilterList).forEach { filter ->
when (filter) {
is TypeFilter -> {
addQueryParameter(filter.param, filter.selection)
}
is StatusFilter -> {
addQueryParameter(filter.param, filter.selection)
}
is LanguageFilter -> {
addQueryParameter(filter.param, filter.selection)
}
is SortFilter -> {
addQueryParameter(filter.param, filter.selection)
}
is GenresFilter -> {
filter.state.forEach {
if (it.state) {
addQueryParameter(filter.param, it.id)
}
}
}
else -> { }
}
}
}
addQueryParameter("p", page.toString())
}.build()
return GET(url, 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,
Filter.Separator(),
TypeFilter(),
StatusFilter(),
LanguageFilter(),
SortFilter(),
GenresFilter(),
)
// =========================== Manga 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 = buildString {
root.selectFirst(".description")?.ownText()?.let { append(it) }
append("\n\n")
root.selectFirst(".manga-name-or")?.ownText()?.let {
if (it.isNotEmpty() && it != mangaTitle) {
append("Alternative Title: ")
append(it)
}
}
}.trim()
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()) {
"著者:" -> item.parseAuthorsTo(this)
"地位:" -> 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 = "#ja-chaps > .chapter-item"
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val id = element.attr("data-id")
element.selectFirst("a")!!.run {
setUrlWithoutDomain(attr("href") + "#$id")
name = selectFirst(".name")?.text() ?: text()
}
}
// =============================== Pages ================================
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.fromCallable {
val id = chapter.url.substringAfterLast("#")
val ajaxHeaders = super.headersBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01")
add("Referer", URLEncoder.encode(baseUrl + chapter.url.substringBeforeLast("#"), "utf-8"))
add("X-Requested-With", "XMLHttpRequest")
}.build()
val ajaxUrl = "$baseUrl/json/chapter?mode=vertical&id=$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(".container-reader-chapter > div > img").map {
val index = it.attr("alt").toInt()
val imgUrl = it.imgAttr()
Page(index, imageUrl = imgUrl)
}
return pageList
}
// ============================= Utilities ==============================
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,110 @@
package eu.kanade.tachiyomi.extension.ja.rawotaku
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 TypeFilter(
values: Array<String> = types.keys.toTypedArray(),
) : Select("タイプ", "type", values) {
override val selection: String
get() = types[values[state]]!!
companion object {
private val types = mapOf(
"全て" to "all",
"Raw Manga" to "Raw Manga",
"BLコミック" to "BLコミック",
"TLコミック" to "TLコミック",
"オトナコミック" to "オトナコミック",
"女性マンガ" to "女性マンガ",
"少女マンガ" to "少女マンガ",
"少年マンガ" to "少年マンガ",
"青年マンガ" to "青年マンガ",
)
}
}
class StatusFilter(
values: Array<String> = statuses.keys.toTypedArray(),
) : Select("地位", "status", values) {
override val selection: String
get() = statuses[values[state]]!!
companion object {
private val statuses = mapOf(
"全て" to "all",
"Publishing" to "Publishing",
"Finished" to "Finished",
)
}
}
class LanguageFilter(
values: Array<String> = languages.keys.toTypedArray(),
) : Select("言語", "language", values) {
override val selection: String
get() = languages[values[state]]!!
companion object {
private val languages = mapOf(
"全て" to "all",
"Japanese" to "ja",
"English" to "en",
)
}
}
class SortFilter(
values: Array<String> = sort.keys.toTypedArray(),
) : Select("選別", "sort", values) {
override val selection: String
get() = sort[values[state]]!!
companion object {
private val sort = mapOf(
"デフォルト" to "default",
"最新の更新" to "latest-updated",
"最も見られました" to "most-viewed",
"Title [A-Z]" to "title-az",
"Title [Z-A]" to "title-za",
)
}
}
class Genre(name: String, val id: String) : Filter.CheckBox(name)
class GenresFilter(
values: List<Genre> = genres,
) : Filter.Group<Genre>("ジャンル", values) {
val param = "genre[]"
companion object {
private val genres: List<Genre>
get() = listOf(
Genre("アクション", "55"),
Genre("エッチ", "15706"),
Genre("コメディ", "91"),
Genre("ドラマ", "56"),
Genre("ハーレム", "20"),
Genre("ファンタジー", "1"),
Genre("冒険", "54"),
Genre("悪魔", "6820"),
Genre("武道", "1064"),
Genre("歴史的", "9600"),
Genre("警察・特殊部隊", "6089"),
Genre("車・バイク", "4329"),
Genre("音楽", "473"),
Genre("魔法", "1416"),
)
}
}

View File

@ -33,6 +33,12 @@ class MangaReaderGenerator : ThemeSourceGenerator {
pkgName = "comickiba",
overrideVersionCode = 33,
),
SingleLang(
name = "Raw Otaku",
baseUrl = "https://rawotaku.com",
lang = "ja",
isNsfw = true,
),
)
companion object {