From 0e384c134d76de6be36e66a89036381f742a786e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 27 Jan 2021 18:24:48 +0200 Subject: [PATCH] Fix some manga sources --- app/build.gradle | 2 +- .../core/exceptions/AuthRequiredException.kt | 13 +++ .../CloudFlareProtectedException.kt | 2 + .../exceptions/resolve/ExceptionResolver.kt | 2 + .../kotatsu/core/model/MangaSource.kt | 3 +- .../kotatsu/core/parser/ParserModule.kt | 2 +- .../core/parser/RemoteMangaRepository.kt | 7 +- .../core/parser/site/GroupleRepository.kt | 7 +- .../core/parser/site/MangaLibRepository.kt | 4 + .../core/parser/site/MangareadRepository.kt | 22 +++-- .../core/parser/site/NudeMoonRepository.kt | 13 +-- .../core/parser/site/SelfMangaRepository.kt | 2 +- .../list/ui/model/ListModelConversionExt.kt | 2 + .../kotatsu/reader/ui/ReaderActivity.kt | 7 +- .../kotatsu/settings/MainSettingsFragment.kt | 2 +- .../koitharu/kotatsu/utils/ext/CommonExt.kt | 6 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- .../kotatsu/parsers/RemoteRepositoryTest.kt | 84 +++++++++++++------ .../kotatsu/parsers/TemporaryCookieJar.kt | 16 +++- .../org/koitharu/kotatsu/utils/AssertX.kt | 10 +-- build.gradle | 2 +- 22 files changed, 138 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/exceptions/AuthRequiredException.kt diff --git a/app/build.gradle b/app/build.gradle index d5889c5d1..921490d78 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ android { } testOptions { unitTests.includeAndroidResources = true - unitTests.returnDefaultValues = true + unitTests.returnDefaultValues = false } } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/AuthRequiredException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/AuthRequiredException.kt new file mode 100644 index 000000000..9067878fb --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/AuthRequiredException.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.core.exceptions + +import androidx.annotation.StringRes +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException + +class AuthRequiredException( + val url: String +) : RuntimeException("Authorization required"), ResolvableException { + + @StringRes + override val resolveTextId: Int = R.string.sign_in +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt index db2cdca33..e02f16da1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.exceptions +import androidx.annotation.StringRes import okio.IOException import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException @@ -8,5 +9,6 @@ class CloudFlareProtectedException( val url: String ) : IOException("Protected by CloudFlare"), ResolvableException { + @StringRes override val resolveTextId: Int = R.string.captcha_solve } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt index 906773ab8..e4d4fdfb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt @@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner import kotlinx.coroutines.suspendCancellableCoroutine import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog +import org.koitharu.kotatsu.core.exceptions.AuthRequiredException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -18,6 +19,7 @@ class ExceptionResolver( suspend fun resolve(e: ResolvableException): Boolean = when (e) { is CloudFlareProtectedException -> resolveCF(e.url) + is AuthRequiredException -> false //TODO else -> false } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index 232719811..ac67f280d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -26,7 +26,8 @@ enum class MangaSource( YAOICHAN("Яой-тян", "ru", YaoiChanRepository::class.java), MANGATOWN("MangaTown", "en", MangaTownRepository::class.java), MANGALIB("MangaLib", "ru", MangaLibRepository::class.java), - NUDEMOON("Nude-Moon", "ru", NudeMoonRepository::class.java), + + // NUDEMOON("Nude-Moon", "ru", NudeMoonRepository::class.java), MANGAREAD("MangaRead", "en", MangareadRepository::class.java), REMANGA("Remanga", "ru", RemangaRepository::class.java), HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java); diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt index 7dcd82813..b5b3f431c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt @@ -20,7 +20,7 @@ val parserModule factory(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) } factory(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) } factory(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) } - factory(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) } + // factory(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) } factory(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) } factory(named(MangaSource.REMANGA)) { RemangaRepository(get()) } factory(named(MangaSource.HENTAILIB)) { HentaiLibRepository(get()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 3bab2ce54..d1affaaf3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -1,6 +1,6 @@ package org.koitharu.kotatsu.core.parser -import android.net.Uri +import okhttp3.HttpUrl.Companion.toHttpUrl import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaSource @@ -26,10 +26,9 @@ abstract class RemoteMangaRepository( abstract fun onCreatePreferences(): Set protected fun generateUid(url: String): Long { - val uri = Uri.parse(url) - val path = uri.path ?: error("Cannot generate uid: bad uri \"$url\"") + val uri = url.toHttpUrl() val x = source.name.hashCode() - val y = path.hashCode() + val y = "${uri.encodedPath}?${uri.query}".hashCode() return (x.toLong() shl 32) or (y.toLong() and 0xffffffffL) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt index 41a02faff..6aa7ec1a1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt @@ -16,9 +16,10 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) : protected abstract val defaultDomain: String override val sortOrders: Set = EnumSet.of( - SortOrder.UPDATED, SortOrder.POPULARITY, - SortOrder.NEWEST, SortOrder.RATING - //FIXME SortOrder.ALPHABETICAL + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + SortOrder.RATING ) override suspend fun getList( diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt index 726b8deb5..9b4f0142c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt @@ -5,6 +5,7 @@ import androidx.collection.arraySetOf import org.json.JSONArray import org.json.JSONObject import org.koitharu.kotatsu.base.domain.MangaLoaderContext +import org.koitharu.kotatsu.core.exceptions.AuthRequiredException import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.parser.RemoteMangaRepository @@ -144,6 +145,9 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) : override suspend fun getPages(chapter: MangaChapter): List { val doc = loaderContext.httpGet(chapter.url).parseHtml() + if (doc.location()?.endsWith("/register") == true) { + throw AuthRequiredException("/login".inContextOf(doc)) + } val scripts = doc.head().select("script") val pg = doc.body().getElementById("pg").html() .substringAfter('=') diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt index 913f08029..bb315d861 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt @@ -49,7 +49,8 @@ class MangareadRepository( Manga( id = href.longHashCode(), url = href, - coverUrl = div.selectFirst("img").absUrl("src"), + coverUrl = div.selectFirst("img").attr("data-srcset") + .split(',').firstOrNull()?.substringBeforeLast(' ').orEmpty(), title = summary.selectFirst("h3").text(), rating = div.selectFirst("span.total_votes")?.ownText() ?.toFloatOrNull()?.div(5f) ?: -1f, @@ -75,13 +76,17 @@ class MangareadRepository( override suspend fun getTags(): Set { val domain = conf.getDomain(DOMAIN) val doc = loaderContext.httpGet("https://$domain/manga/").parseHtml() - val root = doc.body().getElementById("main-sidebar") - .selectFirst(".genres_wrap") - .selectFirst("ul") - return root.select("li").mapToSet { li -> + val root = doc.body().selectFirst("header") + .selectFirst("ul.second-menu") + return root.select("li").mapNotNullToSet { li -> val a = li.selectFirst("a") + val href = a.attr("href").removeSuffix("/") + .substringAfterLast("genres/", "") + if (href.isEmpty()) { + return@mapNotNullToSet null + } MangaTag( - key = a.attr("href").removeSuffix("/").substringAfterLast('/'), + key = href, title = a.text(), source = MangaSource.MANGAREAD ) @@ -119,7 +124,8 @@ class MangareadRepository( } ?: manga.tags, description = root2.selectFirst("div.description-summary") ?.selectFirst("div.summary__content") - ?.select("p")?.drop(1) + ?.select("p") + ?.filterNot { it.ownText().startsWith("A brief description") } ?.joinToString { it.html() }, chapters = doc2.select("li").asReversed().mapIndexed { i, li -> val a = li.selectFirst("a") @@ -142,7 +148,7 @@ class MangareadRepository( ?: throw ParseException("Root not found") return root.select("div.page-break").map { div -> val img = div.selectFirst("img") - val url = img.absUrl("src") + val url = img.absUrl("data-src") MangaPage( id = url.longHashCode(), url = url, diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt index f83586a16..febfabac1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/NudeMoonRepository.kt @@ -1,15 +1,6 @@ package org.koitharu.kotatsu.core.parser.site -import androidx.collection.arraySetOf -import org.koitharu.kotatsu.base.domain.MangaLoaderContext -import org.koitharu.kotatsu.core.exceptions.ParseException -import org.koitharu.kotatsu.core.model.* -import org.koitharu.kotatsu.core.parser.RemoteMangaRepository -import org.koitharu.kotatsu.core.prefs.SourceSettings -import org.koitharu.kotatsu.utils.ext.* -import java.util.* -import java.util.regex.Pattern - +/* class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) { override val source = MangaSource.NUDEMOON @@ -153,4 +144,4 @@ class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit private const val DEFAULT_DOMAIN = "nude-moon.me" private val pageUrlPatter = Pattern.compile(".*\\?page=[0-9]+$") } -} \ No newline at end of file +}*/ diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt index d4e194300..f83877f81 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/SelfMangaRepository.kt @@ -5,6 +5,6 @@ import org.koitharu.kotatsu.core.model.MangaSource class SelfMangaRepository(loaderContext: MangaLoaderContext) : GroupleRepository(loaderContext) { - override val defaultDomain = "selfmanga.ru" + override val defaultDomain = "selfmanga.live" override val source = MangaSource.SELFMANGA } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt index 834d89247..9f3b2f561 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.exceptions.AuthRequiredException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException import org.koitharu.kotatsu.core.model.Manga @@ -58,6 +59,7 @@ fun Throwable.toErrorFooter() = ErrorFooter( ) private fun getErrorIcon(error: Throwable) = when (error) { + is AuthRequiredException, is CloudFlareProtectedException -> R.drawable.ic_denied_large else -> R.drawable.ic_error_large } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index d89c22c19..8f13146a9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -45,10 +45,7 @@ import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.ScreenOrientationHelper import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.anim.Motion -import org.koitharu.kotatsu.utils.ext.hasGlobalPoint -import org.koitharu.kotatsu.utils.ext.hideAnimated -import org.koitharu.kotatsu.utils.ext.hitTest -import org.koitharu.kotatsu.utils.ext.showAnimated +import org.koitharu.kotatsu.utils.ext.* class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeListener, @@ -213,7 +210,7 @@ class ReaderActivity : BaseFullscreenActivity(), private fun onError(e: Throwable) { val dialog = AlertDialog.Builder(this) .setTitle(R.string.error_occurred) - .setMessage(e.message) + .setMessage(e.getDisplayMessage(resources)) .setPositiveButton(R.string.close, null) if (viewModel.content.value?.pages.isNullOrEmpty()) { dialog.setOnDismissListener { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt index dd611a646..0d6223e85 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt @@ -85,7 +85,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), findPreference(AppSettings.KEY_REMOTE_SOURCES)?.run { val total = MangaSource.values().size - 1 summary = getString( - R.string.enabled_d_from_d, total - settings.hiddenSources.size, total + R.string.enabled_d_of_d, total - settings.hiddenSources.size, total ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt index 14bfe4bbd..81188cbcb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt @@ -4,10 +4,7 @@ import android.content.res.Resources import android.util.Log import kotlinx.coroutines.delay import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException -import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException -import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException -import org.koitharu.kotatsu.core.exceptions.WrongPasswordException +import org.koitharu.kotatsu.core.exceptions.* import java.io.FileNotFoundException import java.net.SocketTimeoutException @@ -28,6 +25,7 @@ suspend inline fun T.retryUntilSuccess(maxAttempts: Int, action: T.() -> } fun Throwable.getDisplayMessage(resources: Resources) = when (this) { + is AuthRequiredException -> resources.getString(R.string.auth_required) is CloudFlareProtectedException -> resources.getString(R.string.captcha_required) is UnsupportedOperationException -> resources.getString(R.string.operation_not_supported) is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6ab5e3b79..d1b6dd2fd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -103,7 +103,7 @@ В этой манге %s. Вы уверены, что хотите сохранить их все? Сохранить мангу Уведомления - Включено %1$d из %2$d + Включено %1$d из %2$d Новые главы Уведомлять об обновлении манги, которую Вы читаете Загрузить @@ -200,4 +200,6 @@ Отпимизация батареи уже отключена Проверка новых глав В обратном порядке + Войти + Для просмотра этого контента требуется авторизация \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff9bb127a..9672c2313 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,7 +104,7 @@ This manga has %s. Do you want to save all of it? Save manga Notifications - Enabled %1$d from %2$d + Enabled %1$d of %2$d New chapters Notify about updates of manga you are reading Download @@ -202,4 +202,6 @@ Disable power optimization New chapters checking Reverse + Sign in + You should authorize to view this content \ No newline at end of file diff --git a/app/src/test/java/org/koitharu/kotatsu/parsers/RemoteRepositoryTest.kt b/app/src/test/java/org/koitharu/kotatsu/parsers/RemoteRepositoryTest.kt index 2efd9c140..d3bce5738 100644 --- a/app/src/test/java/org/koitharu/kotatsu/parsers/RemoteRepositoryTest.kt +++ b/app/src/test/java/org/koitharu/kotatsu/parsers/RemoteRepositoryTest.kt @@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.network.UserAgentInterceptor import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.utils.AssertX +import org.koitharu.kotatsu.utils.ext.isDistinctBy import java.util.concurrent.TimeUnit @RunWith(Parameterized::class) @@ -32,58 +33,93 @@ class RemoteRepositoryTest(source: MangaSource) : KoinTest { @Test fun list() { val list = runBlocking { repo.getList(60) } - Assert.assertFalse(list.isEmpty()) + Assert.assertFalse("List is empty", list.isEmpty()) + Assert.assertTrue("Mangas are not distinct", list.isDistinctBy { it.id }) val item = list.random() - AssertX.assertContentType(item.coverUrl, "image/*") - AssertX.assertContentType(item.url, "text/html", "application/json") - Assert.assertFalse(item.title.isBlank()) + AssertX.assertContentType("Bad cover at ${item.url}", item.coverUrl, "image/*") + AssertX.assertContentType( + "Wrong content type at ${item.url}", + item.url, + "text/html", + "application/json" + ) + Assert.assertFalse("Title is blank at ${item.url}", item.title.isBlank()) } @Test fun search() { val list = runBlocking { repo.getList(0, query = "tail") } - Assert.assertFalse(list.isEmpty()) + Assert.assertFalse("List is empty", list.isEmpty()) + Assert.assertTrue("Mangas are not distinct", list.isDistinctBy { it.id }) val item = list.random() - AssertX.assertContentType(item.coverUrl, "image/*") - AssertX.assertContentType(item.url, "text/html", "application/json") - Assert.assertFalse(item.title.isBlank()) + AssertX.assertContentType("Bad cover at ${item.url}", item.coverUrl, "image/*") + AssertX.assertContentType( + "Wrong content type at ${item.url}", + item.url, + "text/html", + "application/json" + ) + Assert.assertFalse("Title is blank at ${item.url}", item.title.isBlank()) } @Test fun tags() { val tags = runBlocking { repo.getTags() } - Assert.assertFalse(tags.isEmpty()) + Assert.assertFalse("No tags found", tags.isEmpty()) val tag = tags.random() - Assert.assertFalse(tag.key.isBlank()) - Assert.assertFalse(tag.title.isBlank()) + Assert.assertFalse("Tag title is blank for ${tag}", tag.key.isBlank()) + Assert.assertFalse("Tag title is blank for ${tag}", tag.title.isBlank()) val list = runBlocking { repo.getList(0, tag = tag) } - Assert.assertFalse(list.isEmpty()) + Assert.assertFalse("List is empty", list.isEmpty()) val item = list.random() - AssertX.assertContentType(item.coverUrl, "image/*") - AssertX.assertContentType(item.url, "text/html", "application/json") - Assert.assertFalse(item.title.isBlank()) + AssertX.assertContentType("Bad cover at ${item.coverUrl}", item.coverUrl, "image/*") + AssertX.assertContentType( + "Wrong response from ${item.url}", + item.url, + "text/html", + "application/json" + ) + Assert.assertFalse("Title is blank at ${item.url}", item.title.isBlank()) } @Test fun details() { val manga = runBlocking { repo.getList(0) }.random() val details = runBlocking { repo.getDetails(manga) } - Assert.assertFalse(details.chapters.isNullOrEmpty()) - Assert.assertFalse(details.description.isNullOrEmpty()) - val chapter = details.chapters!!.random() - Assert.assertFalse(chapter.name.isBlank()) - AssertX.assertContentType(chapter.url, "text/html", "application/json") + Assert.assertFalse("Chapter is empty at ${details.url}", details.chapters.isNullOrEmpty()) + Assert.assertFalse( + "Description is empty at ${details.url}", + details.description.isNullOrEmpty() + ) + Assert.assertTrue( + "Chapters are not distinct", + details.chapters.orEmpty().isDistinctBy { it.id }) + val chapter = details.chapters?.randomOrNull() ?: return + Assert.assertFalse( + "Chapter name missing at ${details.url}:${chapter.number}", + chapter.name.isBlank() + ) + AssertX.assertContentType( + "Chapter response wrong at ${chapter.url}", + chapter.url, + "text/html", + "application/json" + ) } @Test fun pages() { val manga = runBlocking { repo.getList(0) }.random() val details = runBlocking { repo.getDetails(manga) } - val pages = runBlocking { repo.getPages(details.chapters!!.random()) } - Assert.assertFalse(pages.isEmpty()) - val page = pages.random() + val chapter = checkNotNull(details.chapters?.randomOrNull()) { + "No chapters at ${details.url}" + } + val pages = runBlocking { repo.getPages(chapter) } + Assert.assertFalse("Cannot find any page at ${chapter.url}", pages.isEmpty()) + Assert.assertTrue("Pages are not distinct", pages.isDistinctBy { it.id }) + val page = pages.randomOrNull() ?: return val fullUrl = runBlocking { repo.getPageUrl(page) } - AssertX.assertContentType(fullUrl, "image/*") + AssertX.assertContentType("Wrong page response from $fullUrl", fullUrl, "image/*") } companion object { diff --git a/app/src/test/java/org/koitharu/kotatsu/parsers/TemporaryCookieJar.kt b/app/src/test/java/org/koitharu/kotatsu/parsers/TemporaryCookieJar.kt index 020250565..c04ae731d 100644 --- a/app/src/test/java/org/koitharu/kotatsu/parsers/TemporaryCookieJar.kt +++ b/app/src/test/java/org/koitharu/kotatsu/parsers/TemporaryCookieJar.kt @@ -3,17 +3,25 @@ package org.koitharu.kotatsu.parsers import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.HttpUrl -import org.koitharu.kotatsu.core.network.cookies.cache.SetCookieCache class TemporaryCookieJar : CookieJar { - private val cache = SetCookieCache() + private val cache = HashMap() override fun loadForRequest(url: HttpUrl): List { - return cache.toList() + val time = System.currentTimeMillis() + return cache.values.filter { it.matches(url) && it.expiresAt < time } } override fun saveFromResponse(url: HttpUrl, cookies: List) { - cache.addAll(cookies) + cookies.forEach { + val key = CookieKey(url.host, it.name) + cache[key] = it + } } + + private data class CookieKey( + val host: String, + val name: String + ) } \ No newline at end of file diff --git a/app/src/test/java/org/koitharu/kotatsu/utils/AssertX.kt b/app/src/test/java/org/koitharu/kotatsu/utils/AssertX.kt index 78c87bd80..5c23ae8f4 100644 --- a/app/src/test/java/org/koitharu/kotatsu/utils/AssertX.kt +++ b/app/src/test/java/org/koitharu/kotatsu/utils/AssertX.kt @@ -11,18 +11,14 @@ object AssertX : KoinComponent { private val okHttp by inject() - fun assertContentType(url: String, vararg types: String) { - Assert.assertFalse("URL is empty", url.isEmpty()) + fun assertContentType(message: String, url: String, vararg types: String) { + Assert.assertFalse("URL is empty: $message", url.isEmpty()) val request = Request.Builder() .url(url) .head() .build() val response = okHttp.newCall(request).execute() when (val code = response.code) { - /*HttpURLConnection.HTTP_MOVED_PERM, - HttpURLConnection.HTTP_MOVED_TEMP -> { - assertContentType(cn.getHeaderField("Location"), *types) - }*/ HttpURLConnection.HTTP_OK -> { val type = response.body!!.contentType() Assert.assertTrue(types.any { @@ -30,7 +26,7 @@ object AssertX : KoinComponent { type?.type == x[0] && (x[1] == "*" || type.subtype == x[1]) }) } - else -> Assert.fail("Invalid response code $code at $url") + else -> Assert.fail("Invalid response code $code at $url: $message") } } diff --git a/build.gradle b/build.gradle index 2b1a8978a..e1e612d8b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong