add rawotaku (#1111)
This commit is contained in:
		
							parent
							
								
									95d3671f3d
								
							
						
					
					
						commit
						d8f4f38676
					
				
										
											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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user