mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-25 11:42:47 +01:00
add rawotaku (#1111)
This commit is contained in:
parent
fd045fccc6
commit
b9f753261e
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 |
251
multisrc/overrides/mangareader/rawotaku/src/RawOtaku.kt
Normal file
251
multisrc/overrides/mangareader/rawotaku/src/RawOtaku.kt
Normal 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)
|
||||
}
|
||||
}
|
110
multisrc/overrides/mangareader/rawotaku/src/RawOtakuFilters.kt
Normal file
110
multisrc/overrides/mangareader/rawotaku/src/RawOtakuFilters.kt
Normal 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"),
|
||||
)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user