parent
a548a4fb4a
commit
dccbfba8a6
|
@ -0,0 +1,8 @@
|
|||
ext {
|
||||
extName = "Rawdevart.art"
|
||||
extClass = ".Rawdevartart"
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,115 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart
|
||||
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.ChapterDetailsDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.MangaDetailsDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.PaginatedMangaList
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
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.source.online.HttpSource
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class Rawdevartart : HttpSource() {
|
||||
|
||||
override val name = "Rawdevart.art"
|
||||
|
||||
override val lang = "ja"
|
||||
|
||||
override val baseUrl = "https://rawdevart.art"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override fun popularMangaRequest(page: Int) = searchMangaRequest(
|
||||
page,
|
||||
"",
|
||||
FilterList(
|
||||
SortFilter(1),
|
||||
GenreFilter(genres),
|
||||
),
|
||||
)
|
||||
|
||||
override fun popularMangaParse(response: Response) = searchMangaParse(response)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(
|
||||
page,
|
||||
"",
|
||||
FilterList(
|
||||
SortFilter(0),
|
||||
GenreFilter(genres),
|
||||
),
|
||||
)
|
||||
|
||||
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$baseUrl/spa".toHttpUrl().newBuilder().apply {
|
||||
if (query.isNotEmpty()) {
|
||||
addPathSegment("search")
|
||||
addQueryParameter("query", query)
|
||||
addQueryParameter("page", page.toString())
|
||||
|
||||
return@apply
|
||||
}
|
||||
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { f ->
|
||||
when (f) {
|
||||
is UriFilter -> f.addToUri(this)
|
||||
is GenreFilter -> addPathSegments(f.values[f.state].path)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val data = response.parseAs<PaginatedMangaList>()
|
||||
|
||||
return data.toMangasPage()
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val data = response.parseAs<MangaDetailsDto>()
|
||||
|
||||
return data.toSManga()
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val data = response.parseAs<MangaDetailsDto>()
|
||||
|
||||
return data.toSChapterList()
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val data = response.parseAs<ChapterDetailsDto>()
|
||||
|
||||
return data.toPageList(baseUrl)
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
Filter.Header("Filters are ignored when using text search."),
|
||||
StatusFilter(),
|
||||
SortFilter(),
|
||||
GenreFilter(genres),
|
||||
)
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T =
|
||||
json.decodeFromString(body.string())
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
interface UriFilter {
|
||||
fun addToUri(builder: HttpUrl.Builder)
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val query: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
state: Int = 0,
|
||||
) : UriFilter, Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter(query, vals[state].second)
|
||||
}
|
||||
}
|
||||
|
||||
class StatusFilter : UriPartFilter(
|
||||
"Status",
|
||||
"status",
|
||||
arrayOf(
|
||||
"All" to "",
|
||||
"Ongoing" to "ongoing",
|
||||
"Completed" to "completed",
|
||||
),
|
||||
)
|
||||
|
||||
class SortFilter(state: Int = 1) : UriPartFilter(
|
||||
"Sort by",
|
||||
"sort",
|
||||
arrayOf(
|
||||
"Recently updated" to "",
|
||||
"Most viewed" to "most_viewed",
|
||||
"Most viewed today" to "most_viewed_today",
|
||||
),
|
||||
state,
|
||||
)
|
||||
|
||||
data class Genre(val name: String, val path: String) {
|
||||
override fun toString() = name
|
||||
}
|
||||
|
||||
class GenreFilter(genres: Array<Genre>) : Filter.Select<Genre>("Genre", genres)
|
||||
|
||||
// copy([...$0.querySelectorAll("option")].filter((e) => e.value !== "/all").map((e) => `Genre("${e.textContent}", "${e.value.split("/").slice(1, 3).join("/")}"),`).join("\n"))
|
||||
val genres = arrayOf(
|
||||
Genre("All", "genres"),
|
||||
Genre("action", "genre/85"),
|
||||
Genre("adult", "genre/139"),
|
||||
Genre("adventure", "genre/86"),
|
||||
Genre("Alternative World", "genre/149"),
|
||||
Genre("animated", "genre/140"),
|
||||
Genre("comedy", "genre/87"),
|
||||
Genre("cooking", "genre/134"),
|
||||
Genre("drama", "genre/114"),
|
||||
Genre("ecchi", "genre/88"),
|
||||
Genre("Elves", "genre/150"),
|
||||
Genre("fantasy", "genre/89"),
|
||||
Genre("Food", "genre/152"),
|
||||
Genre("Game", "genre/155"),
|
||||
Genre("gender bender", "genre/111"),
|
||||
Genre("harem", "genre/90"),
|
||||
Genre("historical", "genre/115"),
|
||||
Genre("horror", "genre/127"),
|
||||
Genre("Isekai", "genre/144"),
|
||||
Genre("josei", "genre/130"),
|
||||
Genre("loli", "genre/91"),
|
||||
Genre("Lolicon", "genre/148"),
|
||||
Genre("Magic", "genre/151"),
|
||||
Genre("manhua", "genre/128"),
|
||||
Genre("manhwa", "genre/125"),
|
||||
Genre("martial arts", "genre/126"),
|
||||
Genre("mature", "genre/112"),
|
||||
Genre("mecha", "genre/143"),
|
||||
Genre("medical", "genre/132"),
|
||||
Genre("moe", "genre/141"),
|
||||
Genre("mystery", "genre/121"),
|
||||
Genre("N/A", "genre/156"),
|
||||
Genre("one shot", "genre/142"),
|
||||
Genre("Oneshot", "genre/157"),
|
||||
Genre("psychological", "genre/119"),
|
||||
Genre("romance", "genre/106"),
|
||||
Genre("school life", "genre/108"),
|
||||
Genre("sci fi", "genre/122"),
|
||||
Genre("Sci-fi", "genre/146"),
|
||||
Genre("seinen", "genre/107"),
|
||||
Genre("Shotacon", "genre/154"),
|
||||
Genre("shoujo", "genre/120"),
|
||||
Genre("shoujo ai", "genre/131"),
|
||||
Genre("shounen", "genre/118"),
|
||||
Genre("shounen ai", "genre/109"),
|
||||
Genre("slice of life", "genre/92"),
|
||||
Genre("smut", "genre/123"),
|
||||
Genre("sports", "genre/124"),
|
||||
Genre("supernatural", "genre/93"),
|
||||
Genre("tragedy", "genre/135"),
|
||||
Genre("trap (crossdressing)", "genre/138"),
|
||||
Genre("Updating", "genre/147"),
|
||||
Genre("War", "genre/153"),
|
||||
Genre("webtoons", "genre/116"),
|
||||
Genre("Yaoi", "genre/161"),
|
||||
Genre("yuri", "genre/110"),
|
||||
)
|
|
@ -0,0 +1,88 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart
|
||||
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.ChapterDetailsDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.ChapterDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.MangaDetailsDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.MangaDto
|
||||
import eu.kanade.tachiyomi.extension.ja.rawdevartart.dto.PaginatedMangaList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.Jsoup
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
||||
|
||||
fun PaginatedMangaList.toMangasPage(): MangasPage {
|
||||
val manga = mangaList.map { it.toSManga() }
|
||||
val hasNextPage = (pagi.button?.next ?: 0) != 0
|
||||
|
||||
return MangasPage(manga, hasNextPage)
|
||||
}
|
||||
|
||||
private fun MangaDto.toSManga() = SManga.create().apply {
|
||||
// The website URL is manually calculated using a slugify function that I am too
|
||||
// lazy to reimplement.
|
||||
url = "/spa/manga/$id"
|
||||
title = name
|
||||
thumbnail_url = coverImage
|
||||
}
|
||||
|
||||
fun MangaDetailsDto.toSManga() = SManga.create().apply {
|
||||
title = detail.name
|
||||
author = authors.joinToString { it.name }
|
||||
description = buildString {
|
||||
if (!detail.alternativeName.isNullOrEmpty()) {
|
||||
append("Alternative Title: ")
|
||||
appendLine(detail.alternativeName)
|
||||
appendLine()
|
||||
}
|
||||
|
||||
if (!detail.description.isNullOrEmpty()) {
|
||||
append(detail.description)
|
||||
}
|
||||
}
|
||||
genre = tags.joinToString { it.name }
|
||||
status = if (detail.status) SManga.COMPLETED else SManga.ONGOING
|
||||
thumbnail_url = detail.coverImageFull ?: detail.coverImage
|
||||
}
|
||||
|
||||
fun MangaDetailsDto.toSChapterList() = chapters.map { it.toSChapter(detail.id) }
|
||||
|
||||
private fun ChapterDto.toSChapter(mangaId: Int) = SChapter.create().apply {
|
||||
url = "/spa/manga/$mangaId/$number"
|
||||
name = buildString {
|
||||
append("Chapter ")
|
||||
append(formatChapterNumber(number))
|
||||
|
||||
if (title.isNotEmpty()) {
|
||||
append(": ")
|
||||
append(title)
|
||||
}
|
||||
}
|
||||
chapter_number = number
|
||||
date_upload = runCatching {
|
||||
dateFormat.parse(datePublished)!!.time
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
|
||||
fun ChapterDetailsDto.toPageList(baseUrl: String): List<Page> {
|
||||
val document = Jsoup.parseBodyFragment(detail.content!!, baseUrl)
|
||||
|
||||
return document.select("div.chapter-img canvas").mapIndexed { i, it ->
|
||||
Page(i, imageUrl = it.absUrl("data-srcset"))
|
||||
}
|
||||
}
|
||||
|
||||
private val formatter = DecimalFormat(
|
||||
"#.###",
|
||||
DecimalFormatSymbols().apply { decimalSeparator = '.' },
|
||||
)
|
||||
|
||||
fun formatChapterNumber(chapterNumber: Float): String {
|
||||
return formatter.format(chapterNumber)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ChapterDetailsDto(
|
||||
@SerialName("chapter_detail") val detail: ChapterDto,
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ChapterDto(
|
||||
@SerialName("chapter_id") val id: String,
|
||||
@SerialName("chapter_title") val title: String,
|
||||
@SerialName("chapter_number") val number: Float,
|
||||
@SerialName("chapter_date_published") val datePublished: String,
|
||||
@SerialName("chapter_content") val content: String? = null,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MangaDetailsDto(
|
||||
val detail: MangaDto,
|
||||
val tags: List<TagDto>,
|
||||
val authors: List<AuthorDto>,
|
||||
val chapters: List<ChapterDto>,
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MangaDto(
|
||||
@SerialName("manga_name") val name: String,
|
||||
@SerialName("manga_cover_img") val coverImage: String,
|
||||
@SerialName("manga_id") val id: Int,
|
||||
@SerialName("manga_others_name") val alternativeName: String? = null,
|
||||
@SerialName("manga_status") val status: Boolean = false,
|
||||
@SerialName("manga_description") val description: String? = null,
|
||||
@SerialName("manga_cover_img_full") val coverImageFull: String? = null,
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ButtonData(
|
||||
val prev: Int,
|
||||
val next: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PaginationData(
|
||||
val button: ButtonData? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PaginatedMangaList(
|
||||
@SerialName("manga_list") val mangaList: List<MangaDto>,
|
||||
val pagi: PaginationData,
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.extension.ja.rawdevartart.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TagDto(
|
||||
@SerialName("tag_name") val name: String,
|
||||
@SerialName("tag_id") val id: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuthorDto(
|
||||
@SerialName("author_name") val name: String,
|
||||
@SerialName("author_id") val id: Int,
|
||||
)
|
Loading…
Reference in New Issue