Mangadex api v2 for manga info

(cherry picked from commit 38ec991a15d2eebc7ebd0522f8615c2d8dd7003b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/ApiMangaParser.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/online/handlers/serializers/CoversSerializer.kt
#	app/src/main/java/exh/md/handlers/MangaHandler.kt
#	app/src/main/java/exh/md/handlers/SearchHandler.kt
#	app/src/main/java/exh/md/handlers/serializers/ApiMangaSerializer.kt
#	app/src/main/res/drawable/manga_info_more_gradient.xml
This commit is contained in:
Carlos 2020-11-07 20:29:00 -05:00 committed by Jobobby04
parent 68c12d79ee
commit f1a65edd3a
8 changed files with 124 additions and 165 deletions

View File

@ -165,7 +165,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) { override fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) {
ApiMangaParser(listOf(mdLang)).parseIntoMetadata(metadata, input, preferences.mangaDexForceLatestCovers().get()) ApiMangaParser(listOf(mdLang)).parseIntoMetadata(metadata, input, emptyList())
} }
override suspend fun fetchFollows(): MangasPage { override suspend fun fetchFollows(): MangasPage {

View File

@ -47,7 +47,7 @@ class ApiMangaParser(private val langs: List<String>) {
* *
* Will also save the metadata to the DB if possible * Will also save the metadata to the DB if possible
*/ */
fun parseToManga(manga: SManga, input: Response, forceLatestCover: Boolean): Completable { fun parseToManga(manga: SManga, input: Response, coverUrls: List<String>): Completable {
val mangaId = (manga as? Manga)?.id val mangaId = (manga as? Manga)?.id
val metaObservable = if (mangaId != null) { val metaObservable = if (mangaId != null) {
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
@ -61,7 +61,7 @@ class ApiMangaParser(private val langs: List<String>) {
} }
return metaObservable.map { return metaObservable.map {
parseIntoMetadata(it, input, forceLatestCover) parseIntoMetadata(it, input, coverUrls)
it.copyTo(manga) it.copyTo(manga)
it it
}.flatMapCompletable { }.flatMapCompletable {
@ -72,14 +72,14 @@ class ApiMangaParser(private val langs: List<String>) {
} }
} }
suspend fun parseToManga(manga: MangaInfo, input: Response, forceLatestCover: Boolean, sourceId: Long): MangaInfo { suspend fun parseToManga(manga: MangaInfo, input: Response, coverUrls: List<String>, sourceId: Long): MangaInfo {
val mangaId = db.getManga(manga.key, sourceId).await()?.id val mangaId = db.getManga(manga.key, sourceId).await()?.id
val metadata = if (mangaId != null) { val metadata = if (mangaId != null) {
val flatMetadata = db.getFlatMetadataForManga(mangaId).await() val flatMetadata = db.getFlatMetadataForManga(mangaId).await()
flatMetadata?.raise(metaClass) ?: newMetaInstance() flatMetadata?.raise(metaClass) ?: newMetaInstance()
} else newMetaInstance() } else newMetaInstance()
parseInfoIntoMetadata(metadata, input, forceLatestCover) parseInfoIntoMetadata(metadata, input, coverUrls)
if (mangaId != null) { if (mangaId != null) {
metadata.mangaId = mangaId metadata.mangaId = mangaId
db.insertFlatMetadata(metadata.flatten()).await() db.insertFlatMetadata(metadata.flatten()).await()
@ -88,28 +88,26 @@ class ApiMangaParser(private val langs: List<String>) {
return metadata.createMangaInfo(manga) return metadata.createMangaInfo(manga)
} }
fun parseInfoIntoMetadata(metadata: MangaDexSearchMetadata, input: Response, forceLatestCover: Boolean) = parseIntoMetadata(metadata, input, forceLatestCover) fun parseInfoIntoMetadata(metadata: MangaDexSearchMetadata, input: Response, coverUrls: List<String>) = parseIntoMetadata(metadata, input, coverUrls)
fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response, forceLatestCover: Boolean) { fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response, coverUrls: List<String>) {
with(metadata) { with(metadata) {
try { try {
val networkApiManga = MdUtil.jsonParser.decodeFromString<ApiMangaSerializer>(input.body!!.string()) val networkApiManga = MdUtil.jsonParser.decodeFromString<ApiMangaSerializer>(input.body!!.string())
val networkManga = networkApiManga.manga val networkManga = networkApiManga.data.manga
mdId = MdUtil.getMangaId(input.request.url.toString()) mdId = MdUtil.getMangaId(input.request.url.toString())
mdUrl = input.request.url.toString() mdUrl = input.request.url.toString()
title = MdUtil.cleanString(networkManga.title) title = MdUtil.cleanString(networkManga.title)
val coverList = networkManga.covers thumbnail_url = if (coverUrls.isNotEmpty()) {
thumbnail_url = MdUtil.cdnUrl + coverUrls.last()
if (forceLatestCover && coverList.isNotEmpty()) { } else {
coverList.last() networkManga.mainCover
} else { }
MdUtil.removeTimeParamUrl(networkManga.cover_url)
}
description = MdUtil.cleanDescription(networkManga.description) description = MdUtil.cleanDescription(networkManga.description)
author = MdUtil.cleanString(networkManga.author) author = MdUtil.cleanString(networkManga.author.joinToString())
artist = MdUtil.cleanString(networkManga.artist) artist = MdUtil.cleanString(networkManga.artist.joinToString())
lang_flag = networkManga.lang_flag lang_flag = networkManga.publication?.language
last_chapter_number = networkManga.last_chapter?.toFloatOrNull()?.floor() last_chapter_number = networkManga.lastChapter?.toFloatOrNull()?.floor()
networkManga.rating?.let { networkManga.rating?.let {
rating = it.bayesian ?: it.mean rating = it.bayesian ?: it.mean
@ -124,7 +122,7 @@ class ApiMangaParser(private val langs: List<String>) {
} }
val filteredChapters = filterChapterForChecking(networkApiManga) val filteredChapters = filterChapterForChecking(networkApiManga)
val tempStatus = parseStatus(networkManga.status) val tempStatus = parseStatus(networkManga.publication!!.status)
val publishedOrCancelled = val publishedOrCancelled =
tempStatus == SManga.PUBLICATION_COMPLETE || tempStatus == SManga.CANCELLED tempStatus == SManga.PUBLICATION_COMPLETE || tempStatus == SManga.CANCELLED
if (publishedOrCancelled && isMangaCompleted(networkApiManga, filteredChapters)) { if (publishedOrCancelled && isMangaCompleted(networkApiManga, filteredChapters)) {
@ -134,17 +132,19 @@ class ApiMangaParser(private val langs: List<String>) {
status = tempStatus status = tempStatus
} }
val demographic = FilterHandler.demographics().filter { it.id == networkManga.demographic }.firstOrNull()
val genres = val genres =
networkManga.genres.mapNotNull { FilterHandler.allTypes[it.toString()] } networkManga.tags.mapNotNull { FilterHandler.allTypes[it.toString()] }
.toMutableList() .toMutableList()
if (demographic != null) { networkManga.publication.demographic?.let { demographicInt ->
genres.add(0, demographic.name) val demographic = FilterHandler.demographics().firstOrNull { it.id.toInt() == demographicInt }
if (demographic != null) {
genres.add(0, demographic.name)
}
} }
if (networkManga.hentai == 1) { if (networkManga.isHentai) {
genres.add("Hentai") genres.add("Hentai")
} }
@ -163,41 +163,39 @@ class ApiMangaParser(private val langs: List<String>) {
*/ */
private fun isMangaCompleted( private fun isMangaCompleted(
serializer: ApiMangaSerializer, serializer: ApiMangaSerializer,
filteredChapters: List<Map.Entry<String, ChapterSerializer>> filteredChapters: List<ChapterSerializer>
): Boolean { ): Boolean {
if (filteredChapters.isEmpty() || serializer.manga.last_chapter.isNullOrEmpty()) { val finalChapterNumber = serializer.data.manga.lastChapter
if (filteredChapters.isEmpty() || finalChapterNumber.isNullOrEmpty()) {
return false return false
} }
// just to fix the stupid lint // just to fix the stupid lint
val lastMangaChapter: String? = serializer.manga.last_chapter
val finalChapterNumber = lastMangaChapter!!
if (MdUtil.validOneShotFinalChapters.contains(finalChapterNumber)) { if (MdUtil.validOneShotFinalChapters.contains(finalChapterNumber)) {
filteredChapters.firstOrNull()?.let { filteredChapters.firstOrNull()?.let {
if (isOneShot(it.value, finalChapterNumber)) { if (isOneShot(it, finalChapterNumber)) {
return true return true
} }
} }
} }
val removeOneshots = filteredChapters.asSequence() val removeOneshots = filteredChapters.asSequence()
.map { it.value.chapter?.toDoubleOrNull()?.floor()?.nullIfZero() } .map { it.chapter?.toDoubleOrNull()?.floor()?.nullIfZero() }
.filterNotNull() .filterNotNull()
.toList().distinctBy { it } .toList().distinctBy { it }
return removeOneshots.toList().size == finalChapterNumber.toDouble().floor() return removeOneshots.toList().size == finalChapterNumber.toDouble().floor()
} }
private fun filterChapterForChecking(serializer: ApiMangaSerializer): List<Map.Entry<String, ChapterSerializer>> { private fun filterChapterForChecking(serializer: ApiMangaSerializer): List<ChapterSerializer> {
serializer.chapter ?: return emptyList() return serializer.data.chapters.asSequence()
return serializer.chapter.entries .filter { langs.contains(it.language) }
.filter { langs.contains(it.value.lang_code) }
.filter { .filter {
it.value.chapter?.let { chapterNumber -> it.chapter?.let { chapterNumber ->
if (chapterNumber.toDoubleOrNull() == null) { if (chapterNumber.toDoubleOrNull() == null) {
return@filter false return@filter false
} }
return@filter true return@filter true
} }
return@filter false return@filter false
}.distinctBy { it.value.chapter } }.toList()
} }
private fun isOneShot(chapter: ChapterSerializer, finalChapterNumber: String): Boolean { private fun isOneShot(chapter: ChapterSerializer, finalChapterNumber: String): Boolean {
@ -230,24 +228,20 @@ class ApiMangaParser(private val langs: List<String>) {
fun chapterListParse(jsonData: String): List<SChapter> { fun chapterListParse(jsonData: String): List<SChapter> {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val networkApiManga = MdUtil.jsonParser.decodeFromString<ApiMangaSerializer>(jsonData) val networkApiManga = MdUtil.jsonParser.decodeFromString<ApiMangaSerializer>(jsonData)
val networkManga = networkApiManga.manga val networkManga = networkApiManga.data.manga
val networkChapters = networkApiManga.chapter val networkChapters = networkApiManga.data.chapters
if (networkChapters.isNullOrEmpty()) { val groups = networkApiManga.data.groups
return listOf()
}
val status = networkManga.status
val finalChapterNumber = networkManga.last_chapter!! val status = networkManga.publication!!.status
val chapters = mutableListOf<SChapter>() val finalChapterNumber = networkManga.lastChapter
// Skip chapters that don't match the desired language, or are future releases // Skip chapters that don't match the desired language, or are future releases
val chapLangs = MdLang.values().filter { langs.contains(it.dexLang) } val chapLangs = MdLang.values().filter { langs.contains(it.dexLang) }
networkChapters.filter { langs.contains(it.value.lang_code) && (it.value.timestamp * 1000) <= now } return networkChapters.asSequence()
.mapTo(chapters) { mapChapter(it.key, it.value, finalChapterNumber, status, chapLangs, networkChapters.size) } .filter { langs.contains(it.language) && (it.timestamp * 1000) <= now }
.map { mapChapter(it, finalChapterNumber, status, chapLangs, networkChapters.size, groups) }.toList()
return chapters
} }
fun chapterParseForMangaId(response: Response): Int { fun chapterParseForMangaId(response: Response): Int {
@ -267,15 +261,15 @@ class ApiMangaParser(private val langs: List<String>) {
} }
private fun mapChapter( private fun mapChapter(
chapterId: String,
networkChapter: ChapterSerializer, networkChapter: ChapterSerializer,
finalChapterNumber: String, finalChapterNumber: String?,
status: Int, status: Int,
chapLangs: List<MdLang>, chapLangs: List<MdLang>,
totalChapterCount: Int totalChapterCount: Int,
groups: Map<Long, String>
): SChapter { ): SChapter {
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.url = MdUtil.apiChapter + chapterId chapter.url = MdUtil.apiChapter + networkChapter.id
val chapterName = mutableListOf<String>() val chapterName = mutableListOf<String>()
// Build chapter name // Build chapter name
@ -305,10 +299,12 @@ class ApiMangaParser(private val langs: List<String>) {
chapterName.add("Oneshot") chapterName.add("Oneshot")
} }
if ((status == 2 || status == 3)) { if ((status == 2 || status == 3)) {
if ((isOneShot(networkChapter, finalChapterNumber) && totalChapterCount == 1) || if (finalChapterNumber != null) {
networkChapter.chapter == finalChapterNumber && finalChapterNumber.toIntOrNull() != 0 if ((isOneShot(networkChapter, finalChapterNumber) && totalChapterCount == 1) ||
) { networkChapter.chapter == finalChapterNumber && finalChapterNumber.toIntOrNull() != 0
chapterName.add("[END]") ) {
chapterName.add("[END]")
}
} }
} }
@ -317,21 +313,13 @@ class ApiMangaParser(private val langs: List<String>) {
chapter.date_upload = networkChapter.timestamp * 1000 chapter.date_upload = networkChapter.timestamp * 1000
val scanlatorName = mutableSetOf<String>() val scanlatorName = mutableSetOf<String>()
networkChapter.group_name?.let { networkChapter.groups.mapNotNull { groups[it] }.forEach { scanlatorName.add(it) }
scanlatorName.add(it)
}
networkChapter.group_name_2?.let {
scanlatorName.add(it)
}
networkChapter.group_name_3?.let {
scanlatorName.add(it)
}
chapter.scanlator = MdUtil.cleanString(MdUtil.getScanlatorString(scanlatorName)) chapter.scanlator = MdUtil.cleanString(MdUtil.getScanlatorString(scanlatorName))
// chapter.mangadex_chapter_id = MdUtil.getChapterId(chapter.url) // chapter.mangadex_chapter_id = MdUtil.getChapterId(chapter.url)
// chapter.language = chapLangs.firstOrNull { it.dexLang == networkChapter.lang_code }?.name // chapter.language = chapLangs.firstOrNull { it.dexLang == networkChapter.language }?.name
return chapter return chapter
} }

View File

@ -4,9 +4,13 @@ import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.md.handlers.serializers.ApiCovers
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -20,9 +24,10 @@ import tachiyomi.source.model.MangaInfo
class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: List<String>, val forceLatestCovers: Boolean = false) { class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: List<String>, val forceLatestCovers: Boolean = false) {
// TODO make use of this // TODO make use of this
suspend fun fetchMangaAndChapterDetails(manga: SManga): Pair<SManga, List<SChapter>> { suspend fun fetchMangaAndChapterDetails(manga: MangaInfo, sourceId: Long): Pair<MangaInfo, List<SChapter>> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val response = client.newCall(apiRequest(manga)).await() val response = client.newCall(apiRequest(manga.toSManga())).await()
val covers = getCovers(manga, forceLatestCovers)
val parser = ApiMangaParser(langs) val parser = ApiMangaParser(langs)
val jsonData = withContext(Dispatchers.IO) { response.body!!.string() } val jsonData = withContext(Dispatchers.IO) { response.body!!.string() }
@ -31,7 +36,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
throw Exception("Error from MangaDex Response code ${response.code} ") throw Exception("Error from MangaDex Response code ${response.code} ")
} }
parser.parseToManga(manga, response, forceLatestCovers).await() parser.parseToManga(manga, response, covers, sourceId)
val chapterList = parser.chapterListParse(jsonData) val chapterList = parser.chapterListParse(jsonData)
Pair( Pair(
manga, manga,
@ -40,6 +45,15 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
} }
} }
suspend fun getCovers(manga: MangaInfo, forceLatestCovers: Boolean): List<String> {
return if (forceLatestCovers) {
val covers = client.newCall(coverRequest(manga.toSManga())).await().parseAs<ApiCovers>()
covers.data.map { it.url }
} else {
emptyList()
}
}
suspend fun getMangaIdFromChapterId(urlChapterId: String): Int { suspend fun getMangaIdFromChapterId(urlChapterId: String): Int {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val request = GET(MdUtil.baseUrl + MdUtil.apiChapter + urlChapterId + MdUtil.apiChapterSuffix, headers, CacheControl.FORCE_NETWORK) val request = GET(MdUtil.baseUrl + MdUtil.apiChapter + urlChapterId + MdUtil.apiChapterSuffix, headers, CacheControl.FORCE_NETWORK)
@ -48,28 +62,26 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
} }
} }
suspend fun fetchMangaDetails(manga: SManga): SManga {
return withContext(Dispatchers.IO) {
val response = client.newCall(apiRequest(manga)).await()
ApiMangaParser(langs).parseToManga(manga, response, forceLatestCovers).await()
manga.apply {
initialized = true
}
}
}
suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long): MangaInfo { suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long): MangaInfo {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val response = client.newCall(apiRequest(manga.toSManga())).await() val response = client.newCall(apiRequest(manga.toSManga())).await()
ApiMangaParser(langs).parseToManga(manga, response, forceLatestCovers, sourceId) val covers = getCovers(manga, forceLatestCovers)
ApiMangaParser(langs).parseToManga(manga, response, covers, sourceId)
} }
} }
fun fetchMangaDetailsObservable(manga: SManga): Observable<SManga> { fun fetchMangaDetailsObservable(manga: SManga): Observable<SManga> {
return client.newCall(apiRequest(manga)) return client.newCall(apiRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.flatMap { response ->
runAsObservable({
getCovers(manga.toMangaInfo(), forceLatestCovers)
}).map {
response to it
}
}
.flatMap { .flatMap {
ApiMangaParser(langs).parseToManga(manga, it, forceLatestCovers).andThen( ApiMangaParser(langs).parseToManga(manga, it.first, it.second).andThen(
Observable.just( Observable.just(
manga.apply { manga.apply {
initialized = true initialized = true
@ -114,6 +126,10 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
} }
private fun apiRequest(manga: SManga): Request { private fun apiRequest(manga: SManga): Request {
return GET(MdUtil.baseUrl + MdUtil.apiManga + MdUtil.getMangaId(manga.url), headers, CacheControl.FORCE_NETWORK) return GET(MdUtil.baseUrl + MdUtil.apiManga + MdUtil.getMangaId(manga.url) + MdUtil.includeChapters, headers, CacheControl.FORCE_NETWORK)
}
private fun coverRequest(manga: SManga): Request {
return GET(MdUtil.baseUrl + MdUtil.apiManga + MdUtil.getMangaId(manga.url) + MdUtil.apiCovers, headers, CacheControl.FORCE_NETWORK)
} }
} }

View File

@ -1,6 +1,5 @@
package exh.md.handlers package exh.md.handlers
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -17,13 +16,10 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy
// Unused, kept for reference todo // Unused, kept for reference todo
class SearchHandler(val client: OkHttpClient, private val headers: Headers, val langs: List<String>, private val useLowQualityCovers: Boolean) { class SearchHandler(val client: OkHttpClient, private val headers: Headers, val langs: List<String>, private val useLowQualityCovers: Boolean) {
private val preferences: PreferencesHelper by injectLazy()
fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when { return when {
query.startsWith(PREFIX_ID_SEARCH) -> { query.startsWith(PREFIX_ID_SEARCH) -> {
@ -33,7 +29,7 @@ class SearchHandler(val client: OkHttpClient, private val headers: Headers, val
.map { response -> .map { response ->
val details = SManga.create() val details = SManga.create()
details.url = "/manga/$realQuery/" details.url = "/manga/$realQuery/"
ApiMangaParser(langs).parseToManga(details, response, preferences.mangaDexForceLatestCovers().get()).await() ApiMangaParser(langs).parseToManga(details, response, emptyList()).await()
MangasPage(listOf(details), false) MangasPage(listOf(details), false)
} }
} }

View File

@ -4,53 +4,39 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class ApiMangaSerializer( data class ApiMangaSerializer(
val chapter: Map<String, ChapterSerializer>? = null, val data: DataSerializer,
val manga: MangaSerializer,
val status: String val status: String
) )
@Serializable @Serializable
data class MangaSerializer( data class DataSerializer(
val artist: String, val manga: MangaSerializer,
val author: String, val chapters: List<ChapterSerializer>,
val cover_url: String, val groups: Map<Long, String>,
val description: String,
val demographic: String,
val genres: List<Int>,
val covers: List<String>,
val hentai: Int,
val lang_flag: String,
val lang_name: String,
val last_chapter: String? = null,
val links: LinksSerializer? = null,
val rating: RatingSerializer? = null,
val status: Int,
val title: String
) )
@Serializable @Serializable
data class MangaSerializerTwo( data class MangaSerializer(
val artist: List<String>, val artist: List<String>,
val author: List<String>, val author: List<String>,
val mainCover: String, val mainCover: String,
val description: String, val description: String,
val publication: Publication,
val tags: List<Int>, val tags: List<Int>,
// val covers: List<String>,
val isHentai: Boolean, val isHentai: Boolean,
// val lang_flag: String,
// val lang_name: String,
val lastChapter: String? = null, val lastChapter: String? = null,
val publication: PublicationSerializer? = null,
val links: LinksSerializer? = null, val links: LinksSerializer? = null,
val rating: RatingSerializerTwo? = null, val rating: RatingSerializer? = null,
val title: String val title: String
) )
@Serializable @Serializable
data class Publication( data class PublicationSerializer(
val language: String, val language: String? = null,
val status: Int, val status: Int,
val demographic: Int val demographic: Int?
) )
@Serializable @Serializable
@ -72,53 +58,13 @@ data class RatingSerializer(
val users: String? = null val users: String? = null
) )
@Serializable
data class RatingSerializerTwo(
val bayesian: Float? = null,
val mean: Float? = null,
val users: Int? = null
)
@Serializable @Serializable
data class ChapterSerializer( data class ChapterSerializer(
val volume: String? = null, val id: Long,
val chapter: String? = null,
val title: String? = null,
val lang_code: String,
val group_id: Int? = null,
val group_name: String? = null,
val group_id_2: Int? = null,
val group_name_2: String? = null,
val group_id_3: Int? = null,
val group_name_3: String? = null,
val timestamp: Long
)
@Serializable
data class ChapterSerializerTwo(
val volume: String? = null, val volume: String? = null,
val chapter: String? = null, val chapter: String? = null,
val title: String? = null, val title: String? = null,
val language: String, val language: String,
val groups: List<GroupSerializer> = emptyList(), val groups: List<Long>,
val timestamp: Long val timestamp: Long
) )
@Serializable
data class GroupSerializer(
val id: Int,
val name: String? = null
)
@Serializable
data class CoversResult(
val covers: List<String> = emptyList(),
val status: String
)
@Serializable
data class ImageReportResult(
val url: String,
val success: Boolean,
val bytes: Int?
)

View File

@ -0,0 +1,14 @@
package exh.md.handlers.serializers
import kotlinx.serialization.Serializable
@Serializable
data class ApiCovers(
val data: List<CoversResult>,
)
@Serializable
data class CoversResult(
val volume: String,
val url: String
)

View File

@ -23,13 +23,14 @@ class MdUtil {
const val cdnUrl = "https://mangadex.org" // "https://s0.mangadex.org" const val cdnUrl = "https://mangadex.org" // "https://s0.mangadex.org"
const val baseUrl = "https://mangadex.org" const val baseUrl = "https://mangadex.org"
const val randMangaPage = "/manga/" const val randMangaPage = "/manga/"
const val apiManga = "/api/manga/" const val apiManga = "/api/v2/manga/"
const val includeChapters = "?include=chapters"
const val apiChapter = "/api/chapter/" const val apiChapter = "/api/chapter/"
const val apiChapterSuffix = "?mark_read=0" const val apiChapterSuffix = "?mark_read=0"
const val groupSearchUrl = "$baseUrl/groups/0/1/" const val groupSearchUrl = "$baseUrl/groups/0/1/"
const val followsAllApi = "/api/?type=manga_follows" const val followsAllApi = "/api/?type=manga_follows"
const val followsMangaApi = "/api/?type=manga_follows&manga_id=" const val followsMangaApi = "/api/?type=manga_follows&manga_id="
const val coversApi = "/api/index.php?type=covers&id=" const val apiCovers = "/covers"
const val reportUrl = "https://api.mangadex.network/report" const val reportUrl = "https://api.mangadex.network/report"
const val imageUrl = "$baseUrl/data" const val imageUrl = "$baseUrl/data"

View File

@ -4,10 +4,8 @@
<gradient <gradient
android:angle="180" android:angle="180"
android:startColor="#ff000000"
android:centerColor="#ff000000" android:centerColor="#ff000000"
android:endColor="#00000000" android:endColor="#00000000" />
android:startColor="#ff000000" />
<corners android:radius="0dp" /> <corners android:radius="0dp" />
</shape> </shape>