diff --git a/lib-multisrc/mangathemesia/assets/i18n/messages_en.properties b/lib-multisrc/mangathemesia/assets/i18n/messages_en.properties index 91e24269f..6b9e8b682 100644 --- a/lib-multisrc/mangathemesia/assets/i18n/messages_en.properties +++ b/lib-multisrc/mangathemesia/assets/i18n/messages_en.properties @@ -28,3 +28,5 @@ genre_missing_warning=Press 'Reset' to attempt to show the genres genre_exclusion_warning=Genre exclusion is not available for all sources project_filter_warning=NOTE: Can't be used with other filter! project_filter_name=%s Project List page +pref_dynamic_url_title=Automatically update dynamic URLs +pref_dynamic_url_summary=Automatically update random numbers in manga URLs.\nHelps mitigating HTTP 404 errors during update and "in library" marks when browsing.\nNote: This setting may require clearing database in advanced settings and migrating all manga to the same source. diff --git a/lib-multisrc/mangathemesia/src/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaAlt.kt b/lib-multisrc/mangathemesia/src/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaAlt.kt index 0d3a1879a..473baf511 100644 --- a/lib-multisrc/mangathemesia/src/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaAlt.kt +++ b/lib-multisrc/mangathemesia/src/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaAlt.kt @@ -13,6 +13,8 @@ import eu.kanade.tachiyomi.util.asJsoup import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString import okhttp3.Request import okhttp3.Response import uy.kohesive.injekt.Injekt @@ -37,58 +39,141 @@ abstract class MangaThemesiaAlt( override fun setupPreferenceScreen(screen: PreferenceScreen) { SwitchPreferenceCompat(screen.context).apply { key = randomUrlPrefKey - title = "Automatically update dynamic URLs" - summary = "Automatically update random numbers in manga URLs.\n" + - "Helps mitigating HTTP 404 errors during update and \"in library\" marks when browsing.\n\n" + - "example: https://example.com/manga/12345-cool-manga -> https://example.com/manga/4567-cool-manga\n\n" + - "Note: This setting may require clearing database in advanced settings\n" + - "and migrating all manga to the same source" + title = intl["pref_dynamic_url_title"] + summary = intl["pref_dynamic_url_summary"] setDefaultValue(true) }.also(screen::addPreference) } private fun getRandomUrlPref() = preferences.getBoolean(randomUrlPrefKey, true) - private var randomPartCache = SuspendLazy(::updateRandomPart) + private var randomPartCache = SuspendLazy(::getUpdatedRandomPart) { preferences.randomPartCache = it } - protected open fun getRandomPart(response: Response): String { + // cache in preference for webview urls + private var SharedPreferences.randomPartCache: String + get() = getString("__random_part_cache", "")!! + set(newValue) = edit().putString("__random_part_cache", newValue).apply() + + // some new titles don't have random part + // se we save their slug and when they + // finally add it, we remove the slug in the interceptor + private var SharedPreferences.titlesWithoutRandomPart: MutableSet + get() { + val value = getString("titles_without_random_part", null) + ?: return mutableSetOf() + + return json.decodeFromString(value) + } + set(newValue) { + val encodedValue = json.encodeToString(newValue) + + edit().putString("titles_without_random_part", encodedValue).apply() + } + + protected open fun getRandomPartFromUrl(url: String): String { + val slug = url + .removeSuffix("/") + .substringAfterLast("/") + + return slugRegex.find(slug)?.groupValues?.get(1) ?: "" + } + + protected open fun getRandomPartFromResponse(response: Response): String { return response.asJsoup() .selectFirst(searchMangaSelector())!! .select("a").attr("href") - .removeSuffix("/") - .substringAfterLast("/") - .substringBefore("-") + .let(::getRandomPartFromUrl) } - protected suspend fun updateRandomPart() = + protected suspend fun getUpdatedRandomPart(): String = client.newCall(GET("$baseUrl$mangaUrlDirectory/", headers)) .await() - .use(::getRandomPart) + .use(::getRandomPartFromResponse) override fun searchMangaParse(response: Response): MangasPage { val mp = super.searchMangaParse(response) if (!getRandomUrlPref()) return mp + // extract random part during browsing to avoid extra call + mp.mangas.firstOrNull()?.run { + val randomPart = getRandomPartFromUrl(url) + + if (randomPart.isNotEmpty()) { + randomPartCache.set(randomPart) + } + } + val mangas = mp.mangas.toPermanentMangaUrls() return MangasPage(mangas, mp.hasNextPage) } protected fun List.toPermanentMangaUrls(): List { + // some absolutely new titles don't have the random part yet + // save them so we know where to not apply it + val foundTitlesWithoutRandomPart = mutableSetOf() + for (i in indices) { - val permaSlug = this[i].url + val slug = this[i].url .removeSuffix("/") .substringAfterLast("/") + + if (slugRegex.find(slug)?.groupValues?.get(1) == null) { + foundTitlesWithoutRandomPart.add(slug) + } + + val permaSlug = slug .replaceFirst(slugRegex, "") this[i].url = "$mangaUrlDirectory/$permaSlug/" } + if (foundTitlesWithoutRandomPart.isNotEmpty()) { + foundTitlesWithoutRandomPart.addAll(preferences.titlesWithoutRandomPart) + + preferences.titlesWithoutRandomPart = foundTitlesWithoutRandomPart + } + return this } - protected open val slugRegex = Regex("""^\d+-""") + protected open val slugRegex = Regex("""^(\d+-)""") + + override val client = super.client.newBuilder() + .addInterceptor { chain -> + val request = chain.request() + val response = chain.proceed(request) + + if (request.url.fragment != "titlesWithoutRandomPart") { + return@addInterceptor response + } + + if (!response.isSuccessful && response.code == 404) { + response.close() + + val slug = request.url.toString() + .substringBefore("#") + .removeSuffix("/") + .substringAfterLast("/") + + preferences.titlesWithoutRandomPart.run { + remove(slug) + + preferences.titlesWithoutRandomPart = this + } + + val randomPart = randomPartCache.blockingGet() + val newRequest = request.newBuilder() + .url("$baseUrl$mangaUrlDirectory/$randomPart$slug/") + .build() + + return@addInterceptor chain.proceed(newRequest) + } + + return@addInterceptor response + } + .build() override fun mangaDetailsRequest(manga: SManga): Request { if (!getRandomUrlPref()) return super.mangaDetailsRequest(manga) @@ -99,9 +184,13 @@ abstract class MangaThemesiaAlt( .substringAfterLast("/") .replaceFirst(slugRegex, "") + if (preferences.titlesWithoutRandomPart.contains(slug)) { + return GET("$baseUrl${manga.url}#titlesWithoutRandomPart") + } + val randomPart = randomPartCache.blockingGet() - return GET("$baseUrl$mangaUrlDirectory/$randomPart-$slug/", headers) + return GET("$baseUrl$mangaUrlDirectory/$randomPart$slug/", headers) } override fun getMangaUrl(manga: SManga): String { @@ -113,8 +202,11 @@ abstract class MangaThemesiaAlt( .substringAfterLast("/") .replaceFirst(slugRegex, "") - // we don't want to make network calls when user simply opens the entry - val randomPart = randomPartCache.peek()?.let { "$it-" } ?: "" + if (preferences.titlesWithoutRandomPart.contains(slug)) { + return "$baseUrl${manga.url}" + } + + val randomPart = randomPartCache.peek() ?: preferences.randomPartCache return "$baseUrl$mangaUrlDirectory/$randomPart$slug/" } @@ -122,15 +214,16 @@ abstract class MangaThemesiaAlt( override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) } -internal class SuspendLazy( - private val initializer: suspend () -> T, +internal class SuspendLazy( + private val initializer: suspend () -> String, + private val saveCache: (String) -> Unit, ) { private val mutex = Mutex() - private var cachedValue: SoftReference? = null + private var cachedValue: SoftReference? = null private var fetchTime = 0L - suspend fun get(): T { + suspend fun get(): String { if (fetchTime + 3600000 < System.currentTimeMillis()) { // reset cache cachedValue = null @@ -144,19 +237,23 @@ internal class SuspendLazy( cachedValue?.get()?.let { return it } - val result = initializer() - cachedValue = SoftReference(result) - fetchTime = System.currentTimeMillis() - result + initializer().also { set(it) } } } - fun peek(): T? { + fun set(newVal: String) { + cachedValue = SoftReference(newVal) + fetchTime = System.currentTimeMillis() + + saveCache(newVal) + } + + fun peek(): String? { return cachedValue?.get() } - fun blockingGet(): T { + fun blockingGet(): String { return runBlocking { get() } } } diff --git a/src/ar/ozulscans/build.gradle b/src/ar/ozulscans/build.gradle index e62e20e0a..4cdece6e6 100644 --- a/src/ar/ozulscans/build.gradle +++ b/src/ar/ozulscans/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'King Of Manga' extClass = '.KingOfManga' themePkg = 'mangathemesia' - baseUrl = 'https://kingofmanga.com' - overrideVersionCode = 3 + baseUrl = 'https://king-ofmanga.com' + overrideVersionCode = 4 } apply from: "$rootDir/common.gradle" diff --git a/src/ar/ozulscans/src/eu/kanade/tachiyomi/extension/ar/ozulscans/KingOfManga.kt b/src/ar/ozulscans/src/eu/kanade/tachiyomi/extension/ar/ozulscans/KingOfManga.kt index ed3584634..88839b939 100644 --- a/src/ar/ozulscans/src/eu/kanade/tachiyomi/extension/ar/ozulscans/KingOfManga.kt +++ b/src/ar/ozulscans/src/eu/kanade/tachiyomi/extension/ar/ozulscans/KingOfManga.kt @@ -6,7 +6,7 @@ import java.util.Locale class KingOfManga : MangaThemesiaAlt( "King Of Manga", - "https://kingofmanga.com", + "https://king-ofmanga.com", "ar", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), ) { diff --git a/src/en/asurascans/build.gradle b/src/en/asurascans/build.gradle index f9d11a5ec..57171c6e9 100644 --- a/src/en/asurascans/build.gradle +++ b/src/en/asurascans/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.AsuraScans' themePkg = 'mangathemesia' baseUrl = 'https://asuratoon.com' - overrideVersionCode = 2 + overrideVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/en/luminousscans/build.gradle b/src/en/luminousscans/build.gradle index 8cb7d2d9d..32aa6a3d9 100644 --- a/src/en/luminousscans/build.gradle +++ b/src/en/luminousscans/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.LuminousScans' themePkg = 'mangathemesia' baseUrl = 'https://lumitoon.com' - overrideVersionCode = 4 + overrideVersionCode = 5 } apply from: "$rootDir/common.gradle" diff --git a/src/en/rizzcomic/build.gradle b/src/en/rizzcomic/build.gradle index 5d7c88f69..ab7846bcf 100644 --- a/src/en/rizzcomic/build.gradle +++ b/src/en/rizzcomic/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.RizzComic' themePkg = 'mangathemesia' baseUrl = 'https://rizzcomic.com' - overrideVersionCode = 1 + overrideVersionCode = 2 } apply from: "$rootDir/common.gradle" diff --git a/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt index 4c687f3fd..588404b6c 100644 --- a/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt +++ b/src/en/rizzcomic/src/eu/kanade/tachiyomi/extension/en/rizzcomic/RizzComic.kt @@ -41,7 +41,7 @@ class RizzComic : MangaThemesiaAlt( override val versionId = 2 - override val slugRegex = Regex("""^r\d+-""") + override val slugRegex = Regex("""^(r\d+-)""") // don't allow disabling random part setting override fun setupPreferenceScreen(screen: PreferenceScreen) { }