Add WeebDex (#11113)
* Add WeebDex * fixes and add scanlator info * add placeholder icons * Refactoring with reviewed changes * minor refactoring to fix build * Url and Chapter title fixes fix: Add manga URL correctly. feat: Improve chapter title format by including volume and chapter numbers, and a "Oneshot" fallback. fix: An issue where oneshot chapters show Chapter null * Remove extraneous json field
This commit is contained in:
parent
99dde3ca4d
commit
141b80aa19
7
src/en/weebdex/build.gradle
Normal file
7
src/en/weebdex/build.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'WeebDex'
|
||||||
|
extClass = '.WeebDex'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
BIN
src/en/weebdex/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/weebdex/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/en/weebdex/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/weebdex/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/en/weebdex/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/weebdex/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/en/weebdex/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/weebdex/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
BIN
src/en/weebdex/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/weebdex/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,173 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.weebdex
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.dto.ChapterDto
|
||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.dto.ChapterListDto
|
||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.dto.MangaDto
|
||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.dto.MangaListDto
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
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 keiyoushi.utils.parseAs
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class WeebDex : HttpSource() {
|
||||||
|
override val name = "WeebDex"
|
||||||
|
override val baseUrl = "https://weebdex.org"
|
||||||
|
override val lang = "en"
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
|
.rateLimit(WeebDexConstants.RATE_LIMIT)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
|
// -------------------- Popular --------------------
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val url = WeebDexConstants.API_MANGA_URL.toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("sort", "views")
|
||||||
|
.addQueryParameter("order", "desc")
|
||||||
|
.addQueryParameter("hasChapters", "1")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val mangaListDto = response.parseAs<MangaListDto>()
|
||||||
|
val mangas = mangaListDto.toSMangaList()
|
||||||
|
return MangasPage(mangas, mangaListDto.hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Latest --------------------
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val url = WeebDexConstants.API_MANGA_URL.toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("sort", "updatedAt")
|
||||||
|
.addQueryParameter("order", "desc")
|
||||||
|
.addQueryParameter("hasChapters", "1")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
// -------------------- Search --------------------
|
||||||
|
override fun getFilterList(): FilterList = buildFilterList()
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val urlBuilder = WeebDexConstants.API_MANGA_URL.toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
urlBuilder.addQueryParameter("title", query)
|
||||||
|
} else {
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is TagList -> {
|
||||||
|
filter.state.forEach { tag ->
|
||||||
|
if (tag.state) {
|
||||||
|
WeebDexConstants.tags[tag.name]?.let { tagId ->
|
||||||
|
urlBuilder.addQueryParameter("tag", tagId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TagsExcludeFilter -> {
|
||||||
|
filter.state.forEach { tag ->
|
||||||
|
if (tag.state) {
|
||||||
|
WeebDexConstants.tags[tag.name]?.let { tagId ->
|
||||||
|
urlBuilder.addQueryParameter("tagx", tagId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TagModeFilter -> urlBuilder.addQueryParameter("tmod", filter.state.toString())
|
||||||
|
is TagExcludeModeFilter -> urlBuilder.addQueryParameter("txmod", filter.state.toString())
|
||||||
|
else -> { /* Do Nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separated explicitly to be applied even when a search query is applied.
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is SortFilter -> urlBuilder.addQueryParameter("sort", filter.selected)
|
||||||
|
is OrderFilter -> urlBuilder.addQueryParameter("order", filter.selected)
|
||||||
|
is StatusFilter -> filter.selected?.let { urlBuilder.addQueryParameter("status", it) }
|
||||||
|
is DemographicFilter -> filter.selected?.let { urlBuilder.addQueryParameter("demographic", it) }
|
||||||
|
is ContentRatingFilter -> filter.selected?.let { urlBuilder.addQueryParameter("contentRating", it) }
|
||||||
|
is LangFilter -> filter.query?.let { urlBuilder.addQueryParameter("lang", it) }
|
||||||
|
is HasChaptersFilter -> if (filter.state) urlBuilder.addQueryParameter("hasChapters", "1")
|
||||||
|
is YearFromFilter -> filter.state.takeIf { it.isNotEmpty() }?.let { urlBuilder.addQueryParameter("yearFrom", it) }
|
||||||
|
is YearToFilter -> filter.state.takeIf { it.isNotEmpty() }?.let { urlBuilder.addQueryParameter("yearTo", it) }
|
||||||
|
else -> { /* Do Nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(urlBuilder.build(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
// -------------------- Manga details --------------------
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
return GET("${WeebDexConstants.API_URL}${manga.url}", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val manga = response.parseAs<MangaDto>()
|
||||||
|
return manga.toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Chapters --------------------
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
// chapter list is paginated; get all pages
|
||||||
|
return GET("${WeebDexConstants.API_URL}${manga.url}/chapters?order=desc", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val chapters = mutableListOf<SChapter>()
|
||||||
|
|
||||||
|
// Recursively parse pages
|
||||||
|
fun parsePage(chapterListDto: ChapterListDto) {
|
||||||
|
chapters.addAll(chapterListDto.toSChapterList())
|
||||||
|
if (chapterListDto.hasNextPage) {
|
||||||
|
val nextUrl = response.request.url.newBuilder()
|
||||||
|
.setQueryParameter("page", (chapterListDto.page + 1).toString())
|
||||||
|
.build()
|
||||||
|
val nextResponse = client.newCall(GET(nextUrl, headers)).execute()
|
||||||
|
val nextChapterListDto = nextResponse.parseAs<ChapterListDto>()
|
||||||
|
parsePage(nextChapterListDto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePage(response.parseAs<ChapterListDto>())
|
||||||
|
return chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Pages --------------------
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
return GET("${WeebDexConstants.API_URL}${chapter.url}", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val chapter = response.parseAs<ChapterDto>()
|
||||||
|
return chapter.toPageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used")
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.weebdex
|
||||||
|
|
||||||
|
object WeebDexConstants {
|
||||||
|
|
||||||
|
// API Base URLs
|
||||||
|
const val API_URL = "https://api.weebdex.org"
|
||||||
|
const val CDN_URL = "https://srv.notdelta.xyz"
|
||||||
|
|
||||||
|
// API Endpoints
|
||||||
|
const val API_MANGA_URL = "$API_URL/manga"
|
||||||
|
|
||||||
|
// CDN Endpoints
|
||||||
|
const val CDN_COVER_URL = "$CDN_URL/covers"
|
||||||
|
const val CDN_DATA_URL = "$CDN_URL/data"
|
||||||
|
|
||||||
|
// Rate Limit (API is 5 req/s, using conservative value)
|
||||||
|
const val RATE_LIMIT = 3
|
||||||
|
|
||||||
|
// Tags Map
|
||||||
|
val tags = mapOf(
|
||||||
|
// Formats
|
||||||
|
"Oneshot" to "99q3m1plnt",
|
||||||
|
"Web Comic" to "1utcekkc70",
|
||||||
|
"Doujinshi" to "fnvjk3jg1b",
|
||||||
|
"Adaptation" to "pbst9p8bd4",
|
||||||
|
"Full Color" to "6amsrv3w16",
|
||||||
|
"4-Koma" to "jnqtucy8q3",
|
||||||
|
|
||||||
|
// Genres
|
||||||
|
"Action" to "g0eao31zjw",
|
||||||
|
"Adventure" to "pjl8oxd1ld",
|
||||||
|
"Boys' Love" to "1cnfhxwshb",
|
||||||
|
"Comedy" to "onj03z2gnf",
|
||||||
|
"Crime" to "bwec51tbms",
|
||||||
|
"Drama" to "00xq9oqthh",
|
||||||
|
"Fantasy" to "3lhj8r7s6n",
|
||||||
|
"Girls' Love" to "i9w6sjikyd",
|
||||||
|
"Historical" to "mmf28hr2co",
|
||||||
|
"Horror" to "rclreo8b25",
|
||||||
|
"Magical Girls" to "hy189x450f",
|
||||||
|
"Mystery" to "hv0hsu8kje",
|
||||||
|
"Romance" to "o0rm4pweru",
|
||||||
|
"Slice of Life" to "13x7xvq10k",
|
||||||
|
"Sports" to "zsvyg4whkp",
|
||||||
|
"Tragedy" to "85hmqw16y9",
|
||||||
|
|
||||||
|
// Themes
|
||||||
|
"Cooking" to "9wm2j2zl1e",
|
||||||
|
"Crossdressing" to "arjr4qdpgc",
|
||||||
|
"Delinquents" to "h5ioz14hix",
|
||||||
|
"Genderswap" to "25k4gcfnfp",
|
||||||
|
"Magic" to "evt7r78scn",
|
||||||
|
"Monster Girls" to "ddjrvi8vsu",
|
||||||
|
"School Life" to "hobsiukk71",
|
||||||
|
"Shota" to "lu0sbwbs3r",
|
||||||
|
"Supernatural" to "c4rnaci8q6",
|
||||||
|
"Traditional Games" to "aqfqkul8rg",
|
||||||
|
"Vampires" to "djs29flsq6",
|
||||||
|
"Video Games" to "axstzcu7pc",
|
||||||
|
"Office Workers" to "6uytt2873o",
|
||||||
|
"Martial Arts" to "577a4hd52b",
|
||||||
|
"Zombies" to "szg24cwbrm",
|
||||||
|
"Survival" to "mt4vdanhfc",
|
||||||
|
"Police" to "acai4usl79",
|
||||||
|
"Mafia" to "qjuief8bi1",
|
||||||
|
|
||||||
|
// Content Tags
|
||||||
|
"Gore" to "hceia50cf9",
|
||||||
|
"Sexual Violence" to "xh9k4t31ll",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Demographics
|
||||||
|
val demographics = listOf(
|
||||||
|
"Any" to null,
|
||||||
|
"Shounen" to "shounen",
|
||||||
|
"Shoujo" to "shoujo",
|
||||||
|
"Josei" to "josei",
|
||||||
|
"Seinen" to "seinen",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Publication Status
|
||||||
|
val statusList = listOf(
|
||||||
|
"Any" to null,
|
||||||
|
"Ongoing" to "ongoing",
|
||||||
|
"Completed" to "completed",
|
||||||
|
"Hiatus" to "hiatus",
|
||||||
|
"Cancelled" to "cancelled",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
val langList = listOf(
|
||||||
|
"Any" to null,
|
||||||
|
"English" to "en",
|
||||||
|
"Japanese" to "ja",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort Options
|
||||||
|
val sortList = listOf(
|
||||||
|
"Views" to "views",
|
||||||
|
"Updated At" to "updatedAt",
|
||||||
|
"Created At" to "createdAt",
|
||||||
|
"Chapter Update" to "lastUploadedChapterAt",
|
||||||
|
"Title" to "title",
|
||||||
|
"Year" to "year",
|
||||||
|
"Rating" to "rating",
|
||||||
|
"Follows" to "follows",
|
||||||
|
"Chapters" to "chapters",
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.weebdex
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
|
internal fun buildFilterList() = FilterList(
|
||||||
|
Filter.Header("NOTE: Not all filters work on all sources."),
|
||||||
|
Filter.Separator(),
|
||||||
|
SortFilter(),
|
||||||
|
OrderFilter(),
|
||||||
|
StatusFilter(),
|
||||||
|
LangFilter(),
|
||||||
|
DemographicFilter(),
|
||||||
|
HasChaptersFilter(),
|
||||||
|
Filter.Header("Tags (ignored if searching by text)"),
|
||||||
|
TagModeFilter(),
|
||||||
|
TagList(WeebDexConstants.tags.keys.toTypedArray()),
|
||||||
|
Filter.Header("Tags to exclude (ignored if searching by text)"),
|
||||||
|
TagExcludeModeFilter(),
|
||||||
|
TagsExcludeFilter(WeebDexConstants.tags.keys.toTypedArray()),
|
||||||
|
ContentRatingFilter(),
|
||||||
|
YearFromFilter(),
|
||||||
|
YearToFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class SortFilter : Filter.Select<String>(
|
||||||
|
"Sort by",
|
||||||
|
WeebDexConstants.sortList.map { it.first }.toTypedArray(),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String
|
||||||
|
get() = WeebDexConstants.sortList[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OrderFilter : Filter.Select<String>(
|
||||||
|
"Order",
|
||||||
|
arrayOf("Descending", "Ascending"),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String
|
||||||
|
get() = if (state == 0) "desc" else "asc"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class StatusFilter : Filter.Select<String>(
|
||||||
|
"Status",
|
||||||
|
WeebDexConstants.statusList.map { it.first }.toTypedArray(),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String?
|
||||||
|
get() = WeebDexConstants.statusList[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagCheckBox(name: String) : Filter.CheckBox(name, false)
|
||||||
|
class TagList(tags: Array<String>) : Filter.Group<TagCheckBox>("Tags", tags.map { TagCheckBox(it) })
|
||||||
|
class TagsExcludeFilter(tags: Array<String>) : Filter.Group<TagCheckBox>(
|
||||||
|
"Tags to Exclude",
|
||||||
|
tags.map { TagCheckBox(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
class TagModeFilter : Filter.Select<String>(
|
||||||
|
"Tag mode",
|
||||||
|
arrayOf("AND", "OR"), // what user sees
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String
|
||||||
|
get() = if (state == 0) "0" else "1" // backend wants 0=AND, 1=OR
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagExcludeModeFilter : Filter.Select<String>(
|
||||||
|
"Exclude tag mode",
|
||||||
|
arrayOf("OR", "AND"), // what user sees
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String
|
||||||
|
get() = if (state == 0) "0" else "1" // backend wants 0=OR, 1=AND
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DemographicFilter : Filter.Select<String>(
|
||||||
|
"Demographic",
|
||||||
|
WeebDexConstants.demographics.map { it.first }.toTypedArray(),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val selected: String?
|
||||||
|
get() = WeebDexConstants.demographics[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ContentRatingFilter : Filter.Select<String>(
|
||||||
|
"Content Rating",
|
||||||
|
arrayOf("Any", "Safe", "Suggestive", "Erotica", "Pornographic"),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
private val apiValues = arrayOf(null, "safe", "suggestive", "erotica", "pornographic")
|
||||||
|
|
||||||
|
val selected: String?
|
||||||
|
get() = apiValues[state]
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LangFilter : Filter.Select<String>(
|
||||||
|
"Original Language",
|
||||||
|
WeebDexConstants.langList.map { it.first }.toTypedArray(),
|
||||||
|
0,
|
||||||
|
) {
|
||||||
|
val query: String?
|
||||||
|
get() = WeebDexConstants.langList[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class HasChaptersFilter : Filter.CheckBox("Has Chapters", true) {
|
||||||
|
val selected: String?
|
||||||
|
get() = if (state) "1" else null
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class YearFromFilter : Filter.Text("Year (from)")
|
||||||
|
internal class YearToFilter : Filter.Text("Year (to)")
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.WeebDexConstants
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class WeebDexHelper {
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
fun parseStatus(status: String?): Int {
|
||||||
|
return when (status?.lowercase(Locale.ROOT)) {
|
||||||
|
"ongoing" -> SManga.ONGOING
|
||||||
|
"completed" -> SManga.COMPLETED
|
||||||
|
"hiatus" -> SManga.ON_HIATUS
|
||||||
|
"cancelled" -> SManga.CANCELLED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildCoverUrl(mangaId: String, cover: eu.kanade.tachiyomi.extension.en.weebdex.dto.CoverDto?): String? {
|
||||||
|
if (cover == null) return null
|
||||||
|
val ext = cover.ext
|
||||||
|
return "${WeebDexConstants.CDN_COVER_URL}/$mangaId/${cover.id}$ext"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseDate(dateStr: String): Long {
|
||||||
|
return dateFormat.tryParse(dateStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.weebdex.dto
|
||||||
|
|
||||||
|
import WeebDexHelper
|
||||||
|
import eu.kanade.tachiyomi.extension.en.weebdex.WeebDexConstants
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jsoup.parser.Parser
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterListDto(
|
||||||
|
private val data: List<ChapterDto> = emptyList(),
|
||||||
|
val page: Int = 1,
|
||||||
|
val limit: Int = 0,
|
||||||
|
val total: Int = 0,
|
||||||
|
) {
|
||||||
|
val hasNextPage: Boolean
|
||||||
|
get() = page * limit < total
|
||||||
|
fun toSChapterList(): List<SChapter> {
|
||||||
|
return data.map { it.toSChapter() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterDto(
|
||||||
|
private val id: String,
|
||||||
|
private val title: String? = null,
|
||||||
|
private val chapter: String? = null,
|
||||||
|
private val volume: String? = null,
|
||||||
|
@SerialName("published_at") private val publishedAt: String = "",
|
||||||
|
private val data: List<PageData>? = null,
|
||||||
|
@SerialName("data_optimized") private val dataOptimized: List<PageData>? = null,
|
||||||
|
private val relationships: ChapterRelationshipsDto? = null,
|
||||||
|
) {
|
||||||
|
@Contextual
|
||||||
|
private val helper = WeebDexHelper()
|
||||||
|
|
||||||
|
fun toSChapter(): SChapter {
|
||||||
|
val chapterName = mutableListOf<String>()
|
||||||
|
// Build chapter name
|
||||||
|
volume?.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
chapterName.add("Vol.$it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter?.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
chapterName.add("Ch.$it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
title?.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
if (chapterName.isNotEmpty()) {
|
||||||
|
chapterName.add("-")
|
||||||
|
}
|
||||||
|
chapterName.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if volume, chapter and title is empty its a oneshot
|
||||||
|
if (chapterName.isEmpty()) {
|
||||||
|
chapterName.add("Oneshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
return SChapter.create().apply {
|
||||||
|
url = "/chapter/$id"
|
||||||
|
name = Parser.unescapeEntities(chapterName.joinToString(" "), false)
|
||||||
|
chapter_number = chapter?.toFloat() ?: -2F
|
||||||
|
date_upload = helper.parseDate(publishedAt)
|
||||||
|
scanlator = relationships?.groups?.joinToString(", ") { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toPageList(): List<Page> {
|
||||||
|
val pagesArray = dataOptimized ?: data ?: emptyList()
|
||||||
|
val pages = mutableListOf<Page>()
|
||||||
|
|
||||||
|
pagesArray.forEachIndexed { index, pageData ->
|
||||||
|
// pages in spec have 'name' field and images served from /data/{id}/{filename}
|
||||||
|
val filename = pageData.name
|
||||||
|
val chapterId = id
|
||||||
|
val imageUrl = filename?.takeIf { it.isNotBlank() && chapterId.isNotBlank() }
|
||||||
|
?.let { "${WeebDexConstants.CDN_DATA_URL}/$chapterId/$it" }
|
||||||
|
pages.add(Page(index, imageUrl = imageUrl))
|
||||||
|
}
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterRelationshipsDto(
|
||||||
|
val groups: List<NamedEntity> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageData(
|
||||||
|
val name: String? = null,
|
||||||
|
)
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.weebdex.dto
|
||||||
|
|
||||||
|
import WeebDexHelper
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaListDto(
|
||||||
|
private val data: List<MangaDto> = emptyList(),
|
||||||
|
val page: Int = 1,
|
||||||
|
val limit: Int = 0,
|
||||||
|
val total: Int = 0,
|
||||||
|
) {
|
||||||
|
val hasNextPage: Boolean
|
||||||
|
get() = page * limit < total
|
||||||
|
fun toSMangaList(): List<SManga> {
|
||||||
|
return data.map { it.toSManga() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDto(
|
||||||
|
private val id: String,
|
||||||
|
private val title: String,
|
||||||
|
private val description: String = "",
|
||||||
|
private val status: String? = null,
|
||||||
|
val relationships: RelationshipsDto? = null,
|
||||||
|
) {
|
||||||
|
@Contextual
|
||||||
|
private val helper = WeebDexHelper()
|
||||||
|
fun toSManga(): SManga {
|
||||||
|
return SManga.create().apply {
|
||||||
|
title = this@MangaDto.title
|
||||||
|
description = this@MangaDto.description
|
||||||
|
status = helper.parseStatus(this@MangaDto.status)
|
||||||
|
thumbnail_url = helper.buildCoverUrl(id, relationships?.cover)
|
||||||
|
url = "/manga/$id"
|
||||||
|
relationships?.let { rel ->
|
||||||
|
author = rel.authors.joinToString(", ") { it.name }
|
||||||
|
artist = rel.artists.joinToString(", ") { it.name }
|
||||||
|
genre = rel.tags.joinToString(", ") { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class RelationshipsDto(
|
||||||
|
val authors: List<NamedEntity> = emptyList(),
|
||||||
|
val artists: List<NamedEntity> = emptyList(),
|
||||||
|
val tags: List<NamedEntity> = emptyList(),
|
||||||
|
val cover: CoverDto? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class NamedEntity(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class CoverDto(
|
||||||
|
val id: String,
|
||||||
|
val ext: String = ".jpg",
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user