Mangadex fixes

This commit is contained in:
Jobobby04 2021-05-17 12:45:33 -04:00
parent bc871cd2ee
commit 33a590d895
16 changed files with 135 additions and 111 deletions

View File

@ -59,6 +59,8 @@ class MdList(private val context: Context, id: Int) : TrackService(id) {
if (remoteTrack.status != followStatus.int) { if (remoteTrack.status != followStatus.int) {
if (mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus)) { if (mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus)) {
remoteTrack.status = followStatus.int remoteTrack.status = followStatus.int
} else {
track.status = remoteTrack.status
} }
} }

View File

@ -84,6 +84,25 @@ class MangaDex(delegate: HttpSource, val context: Context) :
private fun useLowQualityThumbnail() = false // sourcePreferences.getInt(SHOW_THUMBNAIL_PREF, 0) == LOW_QUALITY private fun useLowQualityThumbnail() = false // sourcePreferences.getInt(SHOW_THUMBNAIL_PREF, 0) == LOW_QUALITY
private val apiMangaParser by lazy {
ApiMangaParser(baseHttpClient, mdLang.lang)
}
private val apiChapterParser by lazy {
ApiChapterParser()
}
private val followsHandler by lazy {
FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, mdList)
}
private val mangaHandler by lazy {
MangaHandler(baseHttpClient, headers, mdLang.lang, apiMangaParser, followsHandler)
}
private val similarHandler by lazy {
SimilarHandler(baseHttpClient, mdLang.lang)
}
private val mangaPlusHandler by lazy {
MangaPlusHandler(network.client)
}
/*override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = /*override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
urlImportFetchSearchManga(context, query) { urlImportFetchSearchManga(context, query) {
importIdToMdId(query) { importIdToMdId(query) {
@ -114,28 +133,28 @@ class MangaDex(delegate: HttpSource, val context: Context) :
}*/ }*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return MangaHandler(baseHttpClient, headers, mdLang.lang, preferences.mangaDexForceLatestCovers().get()).fetchMangaDetailsObservable(manga, id) return mangaHandler.fetchMangaDetailsObservable(manga, id, preferences.mangaDexForceLatestCovers().get())
} }
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo { override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
return MangaHandler(baseHttpClient, headers, mdLang.lang, preferences.mangaDexForceLatestCovers().get()).getMangaDetails(manga, id) return mangaHandler.getMangaDetails(manga, id, preferences.mangaDexForceLatestCovers().get())
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return MangaHandler(baseHttpClient, headers, mdLang.lang, preferences.mangaDexForceLatestCovers().get()).fetchChapterListObservable(manga) return mangaHandler.fetchChapterListObservable(manga)
} }
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> { override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
return MangaHandler(baseHttpClient, headers, mdLang.lang, preferences.mangaDexForceLatestCovers().get()).getChapterList(manga) return mangaHandler.getChapterList(manga)
} }
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return if (chapter.scanlator == "MangaPlus") { return if (chapter.scanlator == "MangaPlus") {
baseHttpClient.newCall(mangaPlusPageListRequest(chapter)) mangaPlusHandler.client.newCall(mangaPlusPageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
val chapterId = ApiChapterParser().externalParse(response) val chapterId = apiChapterParser.externalParse(response)
MangaPlusHandler(baseHttpClient).fetchPageList(chapterId) mangaPlusHandler.fetchPageList(chapterId)
} }
} else super.fetchPageList(chapter) } else super.fetchPageList(chapter)
} }
@ -146,7 +165,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
override fun fetchImage(page: Page): Observable<Response> { override fun fetchImage(page: Page): Observable<Response> {
return if (page.imageUrl?.contains("mangaplus", true) == true) { return if (page.imageUrl?.contains("mangaplus", true) == true) {
MangaPlusHandler(network.client).client.newCall(GET(page.imageUrl!!, headers)) mangaPlusHandler.client.newCall(GET(page.imageUrl!!, headers))
.asObservableSuccess() .asObservableSuccess()
} else super.fetchImage(page) } else super.fetchImage(page)
} }
@ -158,11 +177,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) { override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) {
ApiMangaParser(baseHttpClient, mdLang.lang).parseIntoMetadata(metadata, input, emptyList()) apiMangaParser.parseIntoMetadata(metadata, input, emptyList())
} }
override suspend fun fetchFollows(page: Int): MangasPage { override suspend fun fetchFollows(page: Int): MangasPage {
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).fetchFollows(page) return followsHandler.fetchFollows(page)
} }
override val requiresLogin: Boolean = false override val requiresLogin: Boolean = false
@ -199,6 +218,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
loginHelper.logout(MdUtil.getAuthHeaders(Headers.Builder().build(), preferences, mdList)) loginHelper.logout(MdUtil.getAuthHeaders(Headers.Builder().build(), preferences, mdList))
} catch (e: NoSessionException) { } catch (e: NoSessionException) {
true true
} catch (e: Exception) {
e.message?.equals("HTTP error 405") ?: false
} }
return if (result) { return if (result) {
@ -208,30 +229,30 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun fetchAllFollows(): List<Pair<SManga, MangaDexSearchMetadata>> { override suspend fun fetchAllFollows(): List<Pair<SManga, MangaDexSearchMetadata>> {
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).fetchAllFollows() return followsHandler.fetchAllFollows()
} }
suspend fun updateReadingProgress(track: Track): Boolean { suspend fun updateReadingProgress(track: Track): Boolean {
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).updateReadingProgress(track) return followsHandler.updateReadingProgress(track)
} }
suspend fun updateRating(track: Track): Boolean { suspend fun updateRating(track: Track): Boolean {
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).updateRating(track) return followsHandler.updateRating(track)
} }
override suspend fun fetchTrackingInfo(url: String): Track { override suspend fun fetchTrackingInfo(url: String): Track {
if (!isLogged()) { if (!isLogged()) {
throw Exception("Not Logged in") throw Exception("Not Logged in")
} }
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).fetchTrackingInfo(url) return followsHandler.fetchTrackingInfo(url)
} }
suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> { suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> {
return MangaHandler(baseHttpClient, headers, mdLang.lang).getTrackingInfo(track, useLowQualityThumbnail(), mdList) return mangaHandler.getTrackingInfo(track, mdList)
} }
override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean { override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
return FollowsHandler(baseHttpClient, headers, preferences, mdLang.lang, useLowQualityThumbnail(), mdList).updateFollowStatus(mangaID, followStatus) return followsHandler.updateFollowStatus(mangaID, followStatus)
} }
override fun getFilterHeader(controller: BaseController<*>, onClick: () -> Unit): MangaDexFabHeaderAdapter { override fun getFilterHeader(controller: BaseController<*>, onClick: () -> Unit): MangaDexFabHeaderAdapter {
@ -239,11 +260,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} }
override suspend fun fetchRandomMangaUrl(): String { override suspend fun fetchRandomMangaUrl(): String {
return MangaHandler(baseHttpClient, headers, mdLang.lang).fetchRandomMangaId() return mangaHandler.fetchRandomMangaId()
} }
suspend fun getMangaSimilar(manga: MangaInfo): MangasPage { suspend fun getMangaSimilar(manga: MangaInfo): MangasPage {
return SimilarHandler(baseHttpClient, mdLang.lang, preferences, useLowQualityThumbnail()).getSimilar(manga) return similarHandler.getSimilar(manga)
} }
// todo remove when mangadex gets it cover api // todo remove when mangadex gets it cover api

View File

@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
@ -21,6 +20,7 @@ import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.under
class SourceFilterSheet( class SourceFilterSheet(
activity: Activity, activity: Activity,
@ -155,22 +155,20 @@ class SourceFilterSheet(
private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> { private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> {
recycler.post { recycler.post {
binding.saveSearchBtn.visibility = if (searches.size < MAX_SAVED_SEARCHES) View.VISIBLE else View.GONE binding.saveSearchBtn.isVisible = searches.size under MAX_SAVED_SEARCHES
} }
val chips: MutableList<Chip> = mutableListOf() return searches.withIndex()
.sortedBy { it.value.name }
searches.withIndex().sortedBy { it.value.name }.forEach { (index, search) -> .map { (index, search) ->
val chip = Chip(context).apply { Chip(context).apply {
text = search.name text = search.name
setOnClickListener { onSavedSearchClicked(index) } setOnClickListener { onSavedSearchClicked(index) }
setOnLongClickListener { setOnLongClickListener {
onSavedSearchDeleteClicked(index, search.name); true onSavedSearchDeleteClicked(index, search.name); true
} }
} }
chips += chip
} }
return chips.sortedBy { it.text.toString().toLowerCase() } .sortedBy { it.text.toString().toLowerCase() }
} }
fun hideFilterButton() { fun hideFilterButton() {

View File

@ -114,7 +114,11 @@ class ChaptersSettingsSheet(
if (item is Item.DrawableSelection) { if (item is Item.DrawableSelection) {
val scanlators = presenter.allChapterScanlators.toList() val scanlators = presenter.allChapterScanlators.toList()
val filteredScanlators = presenter.manga.filtered_scanlators?.let { MdUtil.getScanlators(it) } val filteredScanlators = presenter.manga.filtered_scanlators?.let { MdUtil.getScanlators(it) }
val preselected = if (filteredScanlators.isNullOrEmpty()) scanlators.mapIndexed { index, _ -> index }.toIntArray() else filteredScanlators.map { scanlators.indexOf(it) }.toIntArray() val preselected = if (filteredScanlators.isNullOrEmpty()) {
scanlators.mapIndexed { index, _ -> index }
} else {
filteredScanlators.map { scanlators.indexOf(it) }
}.toIntArray()
MaterialDialog(context) MaterialDialog(context)
.title(R.string.select_scanlators) .title(R.string.select_scanlators)

View File

@ -23,7 +23,6 @@ import okhttp3.Response
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
import java.util.Locale import java.util.Locale
class ApiMangaParser(val client: OkHttpClient, private val lang: String) { class ApiMangaParser(val client: OkHttpClient, private val lang: String) {
@ -138,7 +137,7 @@ class ApiMangaParser(val client: OkHttpClient, private val lang: String) {
cover = "https://coverapi.orell.dev/api/v1/mal/manga/$myAnimeListId/cover" cover = "https://coverapi.orell.dev/api/v1/mal/manga/$myAnimeListId/cover"
} }
if (cover == null) { if (cover == null) {
cover = "https://coverapi.orell.dev/api/v1/mdaltimage/manga/$mdUuid/cover" cover = MdUtil.formThumbUrl(mdUuid.toString())
} }
// val filteredChapters = filterChapterForChecking(networkApiManga) // val filteredChapters = filterChapterForChecking(networkApiManga)
@ -155,8 +154,11 @@ class ApiMangaParser(val client: OkHttpClient, private val lang: String) {
// things that will go with the genre tags but aren't actually genre // things that will go with the genre tags but aren't actually genre
val nonGenres = listOfNotNull( val nonGenres = listOfNotNull(
networkManga.publicationDemographic?.let { RaisedTag("Demographic", it.capitalize(Locale.US), MangaDexSearchMetadata.TAG_TYPE_DEFAULT) }, networkManga.publicationDemographic
networkManga.contentRating?.let { RaisedTag("Content Rating", it.capitalize(Locale.US), MangaDexSearchMetadata.TAG_TYPE_DEFAULT) }, ?.let { RaisedTag("Demographic", it.capitalize(Locale.US), MangaDexSearchMetadata.TAG_TYPE_DEFAULT) },
networkManga.contentRating
?.takeUnless { it == "safe" }
?.let { RaisedTag("Content Rating", it.capitalize(Locale.US), MangaDexSearchMetadata.TAG_TYPE_DEFAULT) },
) )
val genres = nonGenres + networkManga.tags val genres = nonGenres + networkManga.tags
@ -239,7 +241,7 @@ class ApiMangaParser(val client: OkHttpClient, private val lang: String) {
} }
fun chapterListParse(chapterListResponse: List<ChapterResponse>, groupMap: Map<String, String>): List<ChapterInfo> { fun chapterListParse(chapterListResponse: List<ChapterResponse>, groupMap: Map<String, String>): List<ChapterInfo> {
val now = Date().time val now = System.currentTimeMillis()
return chapterListResponse.asSequence() return chapterListResponse.asSequence()
.map { .map {

View File

@ -16,6 +16,7 @@ import exh.md.handlers.serializers.MangaListResponse
import exh.md.handlers.serializers.MangaResponse import exh.md.handlers.serializers.MangaResponse
import exh.md.handlers.serializers.MangaStatusListResponse import exh.md.handlers.serializers.MangaStatusListResponse
import exh.md.handlers.serializers.MangaStatusResponse import exh.md.handlers.serializers.MangaStatusResponse
import exh.md.handlers.serializers.ResultResponse
import exh.md.handlers.serializers.UpdateReadingStatus import exh.md.handlers.serializers.UpdateReadingStatus
import exh.md.utils.FollowStatus import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
@ -38,7 +39,6 @@ class FollowsHandler(
val headers: Headers, val headers: Headers,
val preferences: PreferencesHelper, val preferences: PreferencesHelper,
private val lang: String, private val lang: String,
private val useLowQualityCovers: Boolean,
private val mdList: MdList private val mdList: MdList
) { ) {
@ -78,8 +78,7 @@ class FollowsHandler(
return response.map { return response.map {
MdUtil.createMangaEntry( MdUtil.createMangaEntry(
it, it,
lang, lang
useLowQualityCovers
).toSManga() to MangaDexSearchMetadata().apply { ).toSManga() to MangaDexSearchMetadata().apply {
followStatus = FollowStatus.fromDex(statuses[it.data.id]).int followStatus = FollowStatus.fromDex(statuses[it.data.id]).int
} }
@ -135,7 +134,9 @@ class FollowsHandler(
jsonString.toRequestBody("application/json".toMediaType()) jsonString.toRequestBody("application/json".toMediaType())
) )
).await() ).await()
postResult.isSuccessful
val body = postResult.parseAs<ResultResponse>(MdUtil.jsonParser)
body.result == "ok"
} }
} }

View File

@ -25,18 +25,22 @@ import okhttp3.Request
import rx.Observable import rx.Observable
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class MangaHandler(val client: OkHttpClient, val headers: Headers, private val lang: String, private val forceLatestCovers: Boolean = false) { class MangaHandler(
val client: OkHttpClient,
val headers: Headers,
private val lang: String,
private val apiMangaParser: ApiMangaParser,
private val followsHandler: FollowsHandler
) {
suspend fun fetchMangaAndChapterDetails(manga: MangaInfo, sourceId: Long): Pair<MangaInfo, List<ChapterInfo>> { suspend fun fetchMangaAndChapterDetails(manga: MangaInfo, sourceId: Long, forceLatestCovers: Boolean): Pair<MangaInfo, List<ChapterInfo>> {
return withIOContext { return withIOContext {
val response = client.newCall(mangaRequest(manga)).await() val response = client.newCall(mangaRequest(manga)).await()
val covers = getCovers(manga, forceLatestCovers) val covers = getCovers(manga, forceLatestCovers)
val parser = ApiMangaParser(client, lang)
parser.parseToManga(manga, response, covers, sourceId) to getChapterList(manga) apiMangaParser.parseToManga(manga, response, covers, sourceId) to getChapterList(manga)
} }
} }
@ -45,7 +49,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, private val l
val covers = client.newCall(coverRequest(manga)).await().parseAs<ApiCovers>(MdUtil.jsonParser) val covers = client.newCall(coverRequest(manga)).await().parseAs<ApiCovers>(MdUtil.jsonParser)
return covers.data.map { it.url } return covers.data.map { it.url }
} else {*/ } else {*/
return emptyList<String>() return emptyList()
// } // }
} }
@ -53,19 +57,19 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, private val l
return withIOContext { return withIOContext {
val request = GET(MdUtil.chapterUrl + urlChapterId) val request = GET(MdUtil.chapterUrl + urlChapterId)
val response = client.newCall(request).await() val response = client.newCall(request).await()
ApiMangaParser(client, lang).chapterParseForMangaId(response) apiMangaParser.chapterParseForMangaId(response)
} }
} }
suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long): MangaInfo { suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long, forceLatestCovers: Boolean): MangaInfo {
val response = withIOContext { client.newCall(mangaRequest(manga)).await() } val response = withIOContext { client.newCall(mangaRequest(manga)).await() }
val covers = withIOContext { getCovers(manga, forceLatestCovers) } val covers = withIOContext { getCovers(manga, forceLatestCovers) }
return ApiMangaParser(client, lang).parseToManga(manga, response, covers, sourceId) return apiMangaParser.parseToManga(manga, response, covers, sourceId)
} }
fun fetchMangaDetailsObservable(manga: SManga, sourceId: Long): Observable<SManga> { fun fetchMangaDetailsObservable(manga: SManga, sourceId: Long, forceLatestCovers: Boolean): Observable<SManga> {
return runAsObservable({ return runAsObservable({
getMangaDetails(manga.toMangaInfo(), sourceId).toSManga() getMangaDetails(manga.toMangaInfo(), sourceId, forceLatestCovers).toSManga()
}) })
} }
@ -81,7 +85,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, private val l
val groupMap = getGroupMap(results) val groupMap = getGroupMap(results)
ApiMangaParser(client, lang).chapterListParse(results, groupMap) apiMangaParser.chapterListParse(results, groupMap)
} }
} }
@ -109,29 +113,22 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, private val l
suspend fun fetchRandomMangaId(): String { suspend fun fetchRandomMangaId(): String {
return withIOContext { return withIOContext {
val response = client.newCall(randomMangaRequest()).await() val response = client.newCall(randomMangaRequest()).await()
ApiMangaParser(client, lang).randomMangaIdParse(response) apiMangaParser.randomMangaIdParse(response)
} }
} }
suspend fun getTrackingInfo(track: Track, useLowQualityCovers: Boolean, mdList: MdList): Pair<Track, MangaDexSearchMetadata?> { suspend fun getTrackingInfo(track: Track, mdList: MdList): Pair<Track, MangaDexSearchMetadata?> {
return withIOContext { return withIOContext {
val metadata = async { val metadata = async {
val mangaUrl = "/manga/" + MdUtil.getMangaId(track.tracking_url) val mangaUrl = MdUtil.buildMangaUrl(MdUtil.getMangaId(track.tracking_url))
val manga = MangaInfo(mangaUrl, track.title) val manga = MangaInfo(mangaUrl, track.title)
val response = client.newCall(mangaRequest(manga)).await() val response = client.newCall(mangaRequest(manga)).await()
val metadata = MangaDexSearchMetadata() val metadata = MangaDexSearchMetadata()
ApiMangaParser(client, lang).parseIntoMetadata(metadata, response, emptyList()) apiMangaParser.parseIntoMetadata(metadata, response, emptyList())
metadata metadata
} }
val remoteTrack = async { val remoteTrack = async {
FollowsHandler( followsHandler.fetchTrackingInfo(track.tracking_url)
client,
headers,
Injekt.get(),
lang,
useLowQualityCovers,
mdList
).fetchTrackingInfo(track.tracking_url)
} }
remoteTrack.await() to null remoteTrack.await() to null
} }

View File

@ -11,22 +11,28 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Observable import rx.Observable
class PageHandler(val client: OkHttpClient, val headers: Headers, private val dataSaver: Boolean) { class PageHandler(
val client: OkHttpClient,
val headers: Headers,
private val dataSaver: Boolean,
private val apiChapterParser: ApiChapterParser,
private val mangaPlusHandler: MangaPlusHandler
) {
fun fetchPageList(chapter: SChapter): Observable<List<Page>> { fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
if (chapter.scanlator.equals("MangaPlus")) { if (chapter.scanlator.equals("MangaPlus")) {
return client.newCall(pageListRequest(chapter)) return client.newCall(pageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
val chapterId = ApiChapterParser().externalParse(response) val chapterId = apiChapterParser.externalParse(response)
MangaPlusHandler(client).fetchPageList(chapterId) mangaPlusHandler.fetchPageList(chapterId)
} }
} }
return client.newCall(pageListRequest(chapter)) return client.newCall(pageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
val host = MdUtil.atHomeUrlHostUrl("${MdUtil.atHomeUrl}/${MdUtil.getChapterId(chapter.url)}", client) val host = MdUtil.atHomeUrlHostUrl("${MdUtil.atHomeUrl}/${MdUtil.getChapterId(chapter.url)}", client)
ApiChapterParser().pageListParse(response, host, dataSaver) apiChapterParser.pageListParse(response, host, dataSaver)
} }
} }

View File

@ -18,7 +18,7 @@ import rx.Observable
/** /**
* Returns the latest manga from the updates url since it actually respects the users settings * Returns the latest manga from the updates url since it actually respects the users settings
*/ */
class PopularHandler(val client: OkHttpClient, private val headers: Headers, private val lang: String, private val useLowQualityCovers: Boolean) { class PopularHandler(val client: OkHttpClient, private val headers: Headers, private val lang: String) {
fun fetchPopularManga(page: Int): Observable<MangasPage> { fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page)) return client.newCall(popularMangaRequest(page))
@ -42,7 +42,7 @@ class PopularHandler(val client: OkHttpClient, private val headers: Headers, pri
private fun popularMangaParse(response: Response): MangasPage { private fun popularMangaParse(response: Response): MangasPage {
val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser) val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser)
val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total
val mangaList = mlResponse.results.map { MdUtil.createMangaEntry(it, lang, useLowQualityCovers).toSManga() } val mangaList = mlResponse.results.map { MdUtil.createMangaEntry(it, lang).toSManga() }
return MangasPage(mangaList, hasMoreResults) return MangasPage(mangaList, hasMoreResults)
} }
} }

View File

@ -18,7 +18,7 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
class SearchHandler(val client: OkHttpClient, private val headers: Headers, val lang: String, val filterHandler: FilterHandler, private val useLowQualityCovers: Boolean) { class SearchHandler(val client: OkHttpClient, private val headers: Headers, val lang: String, val filterHandler: FilterHandler, private val apiMangaParser: ApiMangaParser) {
fun fetchSearchManga(page: Int, query: String, filters: FilterList, sourceId: Long): Observable<MangasPage> { fun fetchSearchManga(page: Int, query: String, filters: FilterList, sourceId: Long): Observable<MangasPage> {
return if (query.startsWith(PREFIX_ID_SEARCH)) { return if (query.startsWith(PREFIX_ID_SEARCH)) {
@ -28,8 +28,8 @@ class SearchHandler(val client: OkHttpClient, private val headers: Headers, val
.flatMap { response -> .flatMap { response ->
runAsObservable({ runAsObservable({
val mangaResponse = response.parseAs<MangaResponse>(MdUtil.jsonParser) val mangaResponse = response.parseAs<MangaResponse>(MdUtil.jsonParser)
val details = ApiMangaParser(client, lang) val details = apiMangaParser
.parseToManga(MdUtil.createMangaEntry(mangaResponse, lang, useLowQualityCovers), response, emptyList(), sourceId).toSManga() .parseToManga(MdUtil.createMangaEntry(mangaResponse, lang), response, emptyList(), sourceId).toSManga()
MangasPage(listOf(details), false) MangasPage(listOf(details), false)
}) })
} }
@ -45,7 +45,7 @@ class SearchHandler(val client: OkHttpClient, private val headers: Headers, val
private fun searchMangaParse(response: Response): MangasPage { private fun searchMangaParse(response: Response): MangasPage {
val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser) val mlResponse = response.parseAs<MangaListResponse>(MdUtil.jsonParser)
val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total val hasMoreResults = mlResponse.limit + mlResponse.offset < mlResponse.total
val mangaList = mlResponse.results.map { MdUtil.createMangaEntry(it, lang, useLowQualityCovers).toSManga() } val mangaList = mlResponse.results.map { MdUtil.createMangaEntry(it, lang).toSManga() }
return MangasPage(mangaList, hasMoreResults) return MangasPage(mangaList, hasMoreResults)
} }

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.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
@ -15,7 +14,7 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
class SimilarHandler(val client: OkHttpClient, val lang: String, val preferences: PreferencesHelper, private val useLowQualityCovers: Boolean) { class SimilarHandler(val client: OkHttpClient, val lang: String) {
suspend fun getSimilar(manga: MangaInfo): MangasPage { suspend fun getSimilar(manga: MangaInfo): MangasPage {
val response = client.newCall(similarMangaRequest(manga)).await() val response = client.newCall(similarMangaRequest(manga)).await()
@ -30,9 +29,9 @@ class SimilarHandler(val client: OkHttpClient, val lang: String, val preferences
private fun similarMangaParse(response: Response): MangasPage { private fun similarMangaParse(response: Response): MangasPage {
val mangaList = response.parseAs<SimilarMangaResponse>().matches.map { val mangaList = response.parseAs<SimilarMangaResponse>().matches.map {
SManga.create().apply { SManga.create().apply {
url = "/manga/" + it.id url = MdUtil.buildMangaUrl(it.id)
title = MdUtil.cleanString(it.title[lang] ?: it.title["en"]!!) title = MdUtil.cleanString(it.title[lang] ?: it.title["en"]!!)
thumbnail_url = "https://coverapi.orell.dev/api/v1/mdaltimage/manga/${it.id}/cover" thumbnail_url = MdUtil.formThumbUrl(url)
} }
} }
return MangasPage(mangaList, false) return MangasPage(mangaList, false)

View File

@ -24,7 +24,7 @@ data class LoginBodyToken(val session: String, val refresh: String)
* Response after logout * Response after logout
*/ */
@Serializable @Serializable
data class LogoutResponse(val result: String) data class ResultResponse(val result: String)
/** /**
* Check if session token is valid * Check if session token is valid

View File

@ -12,8 +12,8 @@ import exh.md.handlers.serializers.CheckTokenResponse
import exh.md.handlers.serializers.LoginBodyToken import exh.md.handlers.serializers.LoginBodyToken
import exh.md.handlers.serializers.LoginRequest import exh.md.handlers.serializers.LoginRequest
import exh.md.handlers.serializers.LoginResponse import exh.md.handlers.serializers.LoginResponse
import exh.md.handlers.serializers.LogoutResponse
import exh.md.handlers.serializers.RefreshTokenRequest import exh.md.handlers.serializers.RefreshTokenRequest
import exh.md.handlers.serializers.ResultResponse
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@ -78,7 +78,7 @@ class MangaDexLoginHelper(val client: OkHttpClient, val preferences: Preferences
null null
} }
if (response.code == 200 && loginResponse != null) { if (response.code == 200 && loginResponse != null && loginResponse.result == "ok") {
LoginResult.Success(loginResponse.token) LoginResult.Success(loginResponse.token)
} else { } else {
LoginResult.Failure() LoginResult.Failure()
@ -103,7 +103,7 @@ class MangaDexLoginHelper(val client: OkHttpClient, val preferences: Preferences
suspend fun logout(authHeaders: Headers): Boolean { suspend fun logout(authHeaders: Headers): Boolean {
val response = client.newCall(GET(MdUtil.logoutUrl, authHeaders, CacheControl.FORCE_NETWORK)).await() val response = client.newCall(GET(MdUtil.logoutUrl, authHeaders, CacheControl.FORCE_NETWORK)).await()
val body = response.parseAs<LogoutResponse>(MdUtil.jsonParser) val body = response.parseAs<ResultResponse>(MdUtil.jsonParser)
return body.result == "ok" return body.result == "ok"
} }
} }

View File

@ -202,13 +202,12 @@ class MdUtil {
"(zh-Hant)", "(zh-Hant)",
) )
// guess the thumbnail url is .jpg this has a ~80% success rate fun buildMangaUrl(mangaUuid: String): String {
fun formThumbUrl(mangaUrl: String, lowQuality: Boolean): String { return "/manga/$mangaUuid"
var ext = ".jpg"
if (lowQuality) {
ext = ".thumb$ext"
} }
return cdnUrl + "/images/manga/" + getMangaId(mangaUrl) + ext
fun formThumbUrl(mangaUrl: String): String {
return "https://coverapi.orell.dev/api/v1/mdaltimage/manga/${getMangaId(mangaUrl)}/cover"
} }
// Get the ID from the manga url // Get the ID from the manga url
@ -255,9 +254,9 @@ class MdUtil {
return baseUrl + attr return baseUrl + attr
} }
fun getScanlators(scanlators: String?): List<String> { fun getScanlators(scanlators: String?): Set<String> {
if (scanlators.isNullOrBlank()) return emptyList() if (scanlators.isNullOrBlank()) return emptySet()
return scanlators.split(scanlatorSeparator).distinct() return scanlators.split(scanlatorSeparator).toSet()
} }
fun getScanlatorString(scanlators: Set<String>): String { fun getScanlatorString(scanlators: Set<String>): String {
@ -301,32 +300,27 @@ class MdUtil {
fun parseDate(dateAsString: String): Long = fun parseDate(dateAsString: String): Long =
dateFormatter.parse(dateAsString)?.time ?: 0 dateFormatter.parse(dateAsString)?.time ?: 0
fun createMangaEntry(json: MangaResponse, lang: String, lowQualityCovers: Boolean): MangaInfo { fun createMangaEntry(json: MangaResponse, lang: String): MangaInfo {
val key = "/manga/" + json.data.id val key = buildMangaUrl(json.data.id)
return MangaInfo( return MangaInfo(
key = key, key = key,
title = cleanString(json.data.attributes.title[lang] ?: json.data.attributes.title["en"]!!), title = cleanString(json.data.attributes.title[lang] ?: json.data.attributes.title["en"]!!),
cover = formThumbUrl(key, lowQualityCovers) cover = formThumbUrl(key)
) )
} }
fun sessionToken(preferences: PreferencesHelper, mdList: MdList) = preferences.trackToken(mdList).get().nullIfBlank()?.let { fun getLoginBody(preferences: PreferencesHelper, mdList: MdList) = preferences.trackToken(mdList).get().nullIfBlank()?.let {
try { try {
jsonParser.decodeFromString<LoginBodyToken>(it) jsonParser.decodeFromString<LoginBodyToken>(it)
} catch (e: SerializationException) { } catch (e: SerializationException) {
xLogD("Unable to load session token") xLogD("Unable to load login body")
null null
} }
}?.session }
fun refreshToken(preferences: PreferencesHelper, mdList: MdList) = preferences.trackToken(mdList).get().nullIfBlank()?.let { fun sessionToken(preferences: PreferencesHelper, mdList: MdList) = getLoginBody(preferences, mdList)?.session
try {
jsonParser.decodeFromString<LoginBodyToken>(it) fun refreshToken(preferences: PreferencesHelper, mdList: MdList) = getLoginBody(preferences, mdList)?.refresh
} catch (e: SerializationException) {
xLogD("Unable to load session token")
null
}
}?.refresh
fun updateLoginToken(token: LoginBodyToken, preferences: PreferencesHelper, mdList: MdList) { fun updateLoginToken(token: LoginBodyToken, preferences: PreferencesHelper, mdList: MdList) {
preferences.trackToken(mdList).set(jsonParser.encodeToString(token)) preferences.trackToken(mdList).set(jsonParser.encodeToString(token))

View File

@ -44,7 +44,7 @@ class MangaDexSearchMetadata : RaisedSearchMetadata() {
// var maxChapterNumber: Int? = null // var maxChapterNumber: Int? = null
override fun createMangaInfo(manga: MangaInfo): MangaInfo { override fun createMangaInfo(manga: MangaInfo): MangaInfo {
val key = mdUuid?.let { "/manga/$it" } val key = mdUuid?.let { MdUtil.buildMangaUrl(it) }
val title = title val title = title

View File

@ -146,11 +146,11 @@ abstract class RaisedSearchMetadata {
const val TAG_TYPE_VIRTUAL = -2 const val TAG_TYPE_VIRTUAL = -2
fun MutableList<RaisedTag>.toGenreString() = fun MutableList<RaisedTag>.toGenreString() =
(this).filter { it.type != TAG_TYPE_VIRTUAL } this.filter { it.type != TAG_TYPE_VIRTUAL }
.joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name } .joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
fun MutableList<RaisedTag>.toGenreList() = fun MutableList<RaisedTag>.toGenreList() =
(this).filter { it.type != TAG_TYPE_VIRTUAL } this.filter { it.type != TAG_TYPE_VIRTUAL }
.map { (if (it.namespace != null) "${it.namespace}: " else "") + it.name } .map { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
private val module = SerializersModule { private val module = SerializersModule {