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:
parent
68c12d79ee
commit
f1a65edd3a
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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?
|
|
||||||
)
|
|
||||||
|
@ -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
|
||||||
|
)
|
@ -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"
|
||||||
|
|
||||||
|
@ -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>
|
|
Loading…
x
Reference in New Issue
Block a user