diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mdlist/MdList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mdlist/MdList.kt index 5b19eda18..50fde468f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mdlist/MdList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mdlist/MdList.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.toMangaInfo import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.runAsObservable +import eu.kanade.tachiyomi.util.lang.withIOContext import exh.md.utils.FollowStatus import exh.md.utils.MdUtil import tachiyomi.source.model.MangaInfo @@ -46,58 +47,60 @@ class MdList(private val context: Context, id: Int) : TrackService(id) { override suspend fun add(track: Track): Track = update(track) override suspend fun update(track: Track): Track { - val mdex = mdex ?: throw MangaDexNotFoundException() + return withIOContext { + val mdex = mdex ?: throw MangaDexNotFoundException() - val remoteTrack = mdex.fetchTrackingInfo(track.tracking_url) - val followStatus = FollowStatus.fromInt(track.status) + val remoteTrack = mdex.fetchTrackingInfo(track.tracking_url) + val followStatus = FollowStatus.fromInt(track.status) - // this updates the follow status in the metadata - // allow follow status to update - if (remoteTrack.status != followStatus.int) { - mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus) - remoteTrack.status = followStatus.int - // db.insertFlatMetadataAsync(mangaMetadata.flatten()).await() - } - - if (track.score.toInt() > 0) { - mdex.updateRating(track) - } - - // mangadex wont update chapters if manga is not follows this prevents unneeded network call - - if (followStatus != FollowStatus.UNFOLLOWED) { - if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { - track.status = FollowStatus.COMPLETED.int - mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), FollowStatus.COMPLETED) - } - if (followStatus == FollowStatus.PLAN_TO_READ && track.last_chapter_read > 0) { - val newFollowStatus = FollowStatus.READING - track.status = FollowStatus.READING.int - mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), newFollowStatus) - remoteTrack.status = newFollowStatus.int - // db.insertFlatMetadataAsync(mangaMetadata.flatten()).await() + // this updates the follow status in the metadata + // allow follow status to update + if (remoteTrack.status != followStatus.int) { + mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus) + remoteTrack.status = followStatus.int } - mdex.updateReadingProgress(track) - } else if (track.last_chapter_read != 0) { - // When followStatus has been changed to unfollowed 0 out read chapters since dex does - track.last_chapter_read = 0 + if (track.score.toInt() > 0) { + mdex.updateRating(track) + } + + // mangadex wont update chapters if manga is not follows this prevents unneeded network call + + if (followStatus != FollowStatus.UNFOLLOWED) { + if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { + track.status = FollowStatus.COMPLETED.int + mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), FollowStatus.COMPLETED) + } + if (followStatus == FollowStatus.PLAN_TO_READ && track.last_chapter_read > 0) { + val newFollowStatus = FollowStatus.READING + track.status = FollowStatus.READING.int + mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), newFollowStatus) + remoteTrack.status = newFollowStatus.int + } + + mdex.updateReadingProgress(track) + } else if (track.last_chapter_read != 0) { + // When followStatus has been changed to unfollowed 0 out read chapters since dex does + track.last_chapter_read = 0 + } + track } - return track } override fun getCompletionStatus(): Int = FollowStatus.COMPLETED.int - override suspend fun bind(track: Track): Track = update(refresh(track)) + override suspend fun bind(track: Track): Track = update(refresh(track).also { it.status = FollowStatus.READING.int }) override suspend fun refresh(track: Track): Track { - val mdex = mdex ?: throw MangaDexNotFoundException() - val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track) - track.copyPersonalFrom(remoteTrack) - if (track.total_chapters == 0 && mangaMetadata.status == SManga.COMPLETED) { - track.total_chapters = mangaMetadata.maxChapterNumber ?: 0 + return withIOContext { + val mdex = mdex ?: throw MangaDexNotFoundException() + val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track) + track.copyPersonalFrom(remoteTrack) + if (track.total_chapters == 0 && mangaMetadata.status == SManga.COMPLETED) { + track.total_chapters = mangaMetadata.maxChapterNumber ?: 0 + } + track } - return track } fun createInitialTracker(dbManga: Manga, mdManga: Manga = dbManga): Track { @@ -110,16 +113,18 @@ class MdList(private val context: Context, id: Int) : TrackService(id) { } override suspend fun search(query: String): List { - val mdex = mdex ?: throw MangaDexNotFoundException() - return mdex.fetchSearchManga(0, query, mdex.getFilterList()) - .flatMap { page -> - runAsObservable({ - page.mangas.map { - toTrackSearch(mdex.getMangaDetails(it.toMangaInfo())) - } - }) - } - .awaitSingle() + return withIOContext { + val mdex = mdex ?: throw MangaDexNotFoundException() + mdex.fetchSearchManga(0, query, mdex.getFilterList()) + .flatMap { page -> + runAsObservable({ + page.mangas.map { + toTrackSearch(mdex.getMangaDetails(it.toMangaInfo())) + } + }) + } + .awaitSingle() + } } private fun toTrackSearch(mangaInfo: MangaInfo): TrackSearch = TrackSearch.create(TrackManager.MDLIST).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt index 59f16f90c..f9ecb03a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.content.Context import android.content.SharedPreferences import android.net.Uri -import androidx.core.text.HtmlCompat import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -48,12 +47,17 @@ import exh.source.DelegatedHttpSource import exh.ui.metadata.adapters.MangaDexDescriptionAdapter import exh.util.urlImportFetchSearchManga import exh.widget.preference.MangadexLoginDialog +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.int import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.Response +import okio.EOFException import rx.Observable import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.MangaInfo @@ -73,11 +77,10 @@ class MangaDex(delegate: HttpSource, val context: Context) : RandomMangaSource { override val lang: String = delegate.lang - override val headers: Headers - get() = super.headers.newBuilder().apply { - add("X-Requested-With", "XMLHttpRequest") - add("Referer", MdUtil.baseUrl) - }.build() + override val headers: Headers = super.headers.newBuilder().apply { + add("X-Requested-With", "XMLHttpRequest") + add("Referer", MdUtil.baseUrl) + }.build() private val mdLang by lazy { MdLang.values().find { it.lang == lang }?.dexLang ?: lang @@ -198,13 +201,10 @@ class MangaDex(delegate: HttpSource, val context: Context) : add("login_password", password) add("no_js", "1") add("remember_me", "1") + add("two_factor", twoFactorCode) } - twoFactorCode.let { - formBody.add("two_factor", it) - } - - val response = client.newCall( + client.newCall( POST( "${MdUtil.baseUrl}/ajax/actions.ajax.php?function=login", headers, @@ -212,12 +212,13 @@ class MangaDex(delegate: HttpSource, val context: Context) : ) ).await() - withIOContext { response.body?.string() }.let { result -> - if (result != null && result.isEmpty()) { - true + val response = client.newCall(GET(MdUtil.apiUrl + MdUtil.isLoggedInApi, headers)).await() + + withIOContext { response.body?.string() }.let { jsonData -> + if (jsonData != null) { + MdUtil.jsonParser.decodeFromString(jsonData)["code"]?.let { it as? JsonPrimitive }?.int == 200 } else { - val error = result?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() } - throw Exception(error) + throw Exception("Json data was null") } } } @@ -236,8 +237,14 @@ class MangaDex(delegate: HttpSource, val context: Context) : val result = client.newCall( POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build() ).await() - val resultStr = withIOContext { result.body?.string() } - if (resultStr?.contains("success", true) == true) { + try { + val resultStr = withIOContext { result.body?.string() } + if (resultStr?.contains("success", true) == true) { + network.cookieManager.remove(httpUrl) + trackManager.mdList.logout() + return@withIOContext true + } + } catch (e: EOFException) { network.cookieManager.remove(httpUrl) trackManager.mdList.logout() return@withIOContext true @@ -281,7 +288,7 @@ class MangaDex(delegate: HttpSource, val context: Context) : } override suspend fun fetchRandomMangaUrl(): String { - return MangaHandler(client, headers, mdLang).fetchRandomMangaId() + return withIOContext { MangaHandler(client, headers, mdLang).fetchRandomMangaId() } } fun fetchMangaSimilar(manga: Manga): Observable { diff --git a/app/src/main/java/exh/md/handlers/FollowsHandler.kt b/app/src/main/java/exh/md/handlers/FollowsHandler.kt index 2e0d52107..be689acf5 100644 --- a/app/src/main/java/exh/md/handlers/FollowsHandler.kt +++ b/app/src/main/java/exh/md/handlers/FollowsHandler.kt @@ -28,6 +28,7 @@ import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import okio.EOFException class FollowsHandler(val client: OkHttpClient, val headers: Headers, val preferences: PreferencesHelper, private val useLowQualityCovers: Boolean) { @@ -75,7 +76,6 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere /** * fetch follow status used when fetching status for 1 manga */ - private fun followStatusParse(response: Response): Track { val followsPageResult = try { response.parseAs(MdUtil.jsonParser) @@ -84,15 +84,11 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere throw e } - if (followsPageResult.data == null) { - throw Exception("Invalid response ${followsPageResult.code}") - } - val track = Track.create(TrackManager.MDLIST) if (followsPageResult.code == 404) { track.status = FollowStatus.UNFOLLOWED.int } else { - val follow = followsPageResult.data + val follow = followsPageResult.data ?: throw Exception("Invalid response ${followsPageResult.code}") track.status = follow.followType if (follow.chapter.isNotBlank()) { track.last_chapter_read = follow.chapter.toFloat().floor() @@ -153,7 +149,7 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere .await() } - withIOContext { response.body?.string().isNullOrEmpty() } + response.succeeded() } } @@ -172,11 +168,7 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere ) ).await() - withIOContext { - response.body?.string() - .also { xLogD(it) } - .let { it != null && it.isEmpty() } - } + response.succeeded() } } @@ -188,10 +180,21 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere "${MdUtil.baseUrl}/ajax/actions.ajax.php?function=manga_rating&id=$mangaID&rating=${track.score.toInt()}", headers ) - ) - .await() + ).await() - withIOContext { response.body?.string().isNullOrEmpty() } + response.succeeded() + } + } + + private suspend fun Response.succeeded() = withIOContext { + try { + body?.string().let { body -> + (body != null && body.isEmpty()).also { + if (!it) xLogD(body) + } + } + } catch (e: EOFException) { + true } } diff --git a/app/src/main/java/exh/md/handlers/MangaHandler.kt b/app/src/main/java/exh/md/handlers/MangaHandler.kt index b15d542ba..935fe73fd 100644 --- a/app/src/main/java/exh/md/handlers/MangaHandler.kt +++ b/app/src/main/java/exh/md/handlers/MangaHandler.kt @@ -15,6 +15,7 @@ import exh.md.handlers.serializers.ApiCovers import exh.md.handlers.serializers.ApiMangaSerializer import exh.md.utils.MdUtil import exh.metadata.metadata.MangaDexSearchMetadata +import kotlinx.coroutines.async import okhttp3.CacheControl import okhttp3.Headers import okhttp3.OkHttpClient @@ -30,7 +31,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val lang: Str // TODO make use of this suspend fun fetchMangaAndChapterDetails(manga: MangaInfo, sourceId: Long): Pair> { return withIOContext { - val apiNetworkManga = client.newCall(apiRequest(manga)).await().parseAs() + val apiNetworkManga = client.newCall(apiRequest(manga)).await().parseAs(MdUtil.jsonParser) val covers = getCovers(manga, forceLatestCovers) val parser = ApiMangaParser(lang) @@ -120,17 +121,22 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val lang: Str } suspend fun getTrackingInfo(track: Track, useLowQualityCovers: Boolean): Pair { - val mangaUrl = MdUtil.mapMdIdToMangaUrl(MdUtil.getMangaId(track.tracking_url).toInt()) - val manga = MangaInfo(mangaUrl, track.title) - val response = client.newCall(apiRequest(manga)).await() - val metadata = MangaDexSearchMetadata() - ApiMangaParser(lang).parseIntoMetadata(metadata, response, emptyList()) - val remoteTrack = FollowsHandler(client, headers, Injekt.get(), useLowQualityCovers).fetchTrackingInfo(track.tracking_url) - return remoteTrack to metadata + return withIOContext { + val metadata = async { + val mangaUrl = MdUtil.mapMdIdToMangaUrl(MdUtil.getMangaId(track.tracking_url).toInt()) + val manga = MangaInfo(mangaUrl, track.title) + val response = client.newCall(apiRequest(manga)).await() + val metadata = MangaDexSearchMetadata() + ApiMangaParser(lang).parseIntoMetadata(metadata, response, emptyList()) + metadata + } + val remoteTrack = async { FollowsHandler(client, headers, Injekt.get(), useLowQualityCovers).fetchTrackingInfo(track.tracking_url) } + remoteTrack.await() to metadata.await() + } } private fun randomMangaRequest(): Request { - return GET(MdUtil.baseUrl + MdUtil.randMangaPage, cache = CacheControl.Builder().noCache().build()) + return GET(MdUtil.baseUrl + MdUtil.randMangaPage, cache = CacheControl.FORCE_NETWORK) } private fun apiRequest(manga: MangaInfo): Request { diff --git a/app/src/main/java/exh/md/handlers/serializers/FollowsPageSerializer.kt b/app/src/main/java/exh/md/handlers/serializers/FollowsPageSerializer.kt index 4e4873245..6c4509c33 100644 --- a/app/src/main/java/exh/md/handlers/serializers/FollowsPageSerializer.kt +++ b/app/src/main/java/exh/md/handlers/serializers/FollowsPageSerializer.kt @@ -11,7 +11,7 @@ data class FollowsPageSerializer( @Serializable data class FollowsIndividualSerializer( val code: Int, - val data: FollowPage? + val data: FollowPage? = null ) @Serializable diff --git a/app/src/main/java/exh/md/utils/MdUtil.kt b/app/src/main/java/exh/md/utils/MdUtil.kt index f0e25c673..ae0d1337d 100644 --- a/app/src/main/java/exh/md/utils/MdUtil.kt +++ b/app/src/main/java/exh/md/utils/MdUtil.kt @@ -31,6 +31,7 @@ class MdUtil { const val apiChapterSuffix = "?mark_read=0" const val groupSearchUrl = "$baseUrl/groups/0/1/" const val followsAllApi = "/v2/user/me/followed-manga" + const val isLoggedInApi = "/v2/user/me" const val followsMangaApi = "/v2/user/me/manga/" const val apiCovers = "/covers" const val reportUrl = "https://api.mangadex.network/report" @@ -51,10 +52,16 @@ class MdUtil { val englishDescriptionTags = listOf( "[b][u]English:", "[b][u]English", + "English:", + "English :", "[English]:", + "English Translaton:", "[B][ENG][/B]" ) + val bbCodeToRemove = listOf( + "list", "*", "hr", "u", "b", "i", "s", "center", "spoiler=" + ) val descriptionLanguages = listOf( "=FRANCAIS=", "[b] Spanish: [/ b]", @@ -78,19 +85,23 @@ class MdUtil { "\r\n\r\nItalian\r\n", "Arabic /", "Descriptions in Other Languages", - "Español /", - "Español:", + "Espanol", + "[Españ", + "Españ", "Farsi/", "Français", "French - ", "Francois", "French:", + "French/", "French /", "German/", "German /", "Hindi /", + "Bahasa Indonesia", "Indonesia:", "Indonesian:", + "Indonesian :", "Indo:", "[u]Indonesian", "Italian / ", @@ -98,9 +109,16 @@ class MdUtil { "Italian/", "Italiano", "Italian:", + "Italian summary:", "Japanese /", + "Original Japanese", + "Official Japanese Translation", + "Official Chinese Translation", + "Official French Translation", + "Official Indonesian Translation", "Links:", "Pasta-Pizza-Mandolino/Italiano", + "Persian/فارسی", "Persian /فارسی", "Polish /", "Polish Summary /", @@ -108,6 +126,8 @@ class MdUtil { "Polski", "Português", "Portuguese (BR)", + "PT/BR:", + "Pt/Br:", "Pt-Br:", "Portuguese /", "[right]", @@ -115,6 +135,8 @@ class MdUtil { "Résume Français", "RÉSUMÉ FRANCAIS :", "RUS:", + "Ru/Pyc", + "\\r\\nRUS\\r\\n", "Russia/", "Russian /", "Spanish:", @@ -162,23 +184,22 @@ class MdUtil { fun removeTimeParamUrl(url: String): String = url.substringBeforeLast("?") fun cleanString(string: String): String { + var cleanedString = string + + bbCodeToRemove.forEach { + cleanedString = cleanedString.replace("[$it]", "", true) + .replace("[/$it]", "", true) + } + val bbRegex = """\[(\w+)[^]]*](.*?)\[/\1]""".toRegex() - var intermediate = string - .replace("[list]", "", true) - .replace("[/list]", "", true) - .replace("[*]", "") - .replace("[hr]", "", true) - .replace("[u]", "", true) - .replace("[/u]", "", true) - .replace("[b]", "", true) - .replace("[/b]", "", true) // Recursively remove nested bbcode - while (bbRegex.containsMatchIn(intermediate)) { - intermediate = intermediate.replace(bbRegex, "$2") + while (bbRegex.containsMatchIn(cleanedString)) { + cleanedString = cleanedString.replace(bbRegex, "$2") } - return Parser.unescapeEntities(intermediate, false) + + return Parser.unescapeEntities(cleanedString, false) } fun cleanDescription(string: String): String {