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