diff --git a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScans.kt b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScans.kt index 245278d81..d60b61cd5 100644 --- a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScans.kt +++ b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScans.kt @@ -1,22 +1,24 @@ package eu.kanade.tachiyomi.extension.all.asurascans import android.app.Application -import android.content.SharedPreferences -import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit -import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.IOException import java.text.SimpleDateFormat import java.util.concurrent.TimeUnit @@ -29,31 +31,46 @@ open class AsuraScans( baseUrl, lang, dateFormat = dateFormat, -), - ConfigurableSource { - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } +) { + private val preferences = Injekt.get().getSharedPreferences("source_$id", 0x0000) override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor(::urlChangeInterceptor) .addInterceptor(uaIntercept) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .rateLimit(1, 3, TimeUnit.SECONDS) .build() - // Permanent Url for Manga/Chapter End - override fun fetchPopularManga(page: Int): Observable { - return super.fetchPopularManga(page).tempUrlToPermIfNeeded() + override fun fetchChapterList(manga: SManga): Observable> { + val newManga = manga.apply { + url = "$url#chapters#${title.toSearchQuery()}" + } + return super.fetchChapterList(newManga) } - override fun fetchLatestUpdates(page: Int): Observable { - return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded() + override fun fetchMangaDetails(manga: SManga): Observable { + val newManga = try { + manga.apply { + url = "$url#details#${title.toSearchQuery()}" + } + } catch (e: UninitializedPropertyAccessException) { + // when called from deep link, title is not present + manga + } + + return super.fetchMangaDetails(newManga) } - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded() + // use updated url for webView + override fun getMangaUrl(manga: SManga): String { + val dbSlug = manga.url + .removeSuffix("/") + .substringAfterLast("/") + + val storedSlug = getSlugMap()[dbSlug] ?: dbSlug + + return "$baseUrl$mangaUrlDirectory/$storedSlug/" } // Skip scriptPages @@ -70,55 +87,100 @@ open class AsuraScans( else -> attr("abs:src") } - private fun Observable.tempUrlToPermIfNeeded(): Observable { - return this.map { mangasPage -> - MangasPage( - mangasPage.mangas.map { it.tempUrlToPermIfNeeded() }, - mangasPage.hasNextPage, - ) + private fun urlChangeInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val frag = request.url.fragment + + if (frag.isNullOrEmpty()) { + return chain.proceed(request) } + + val search = frag.substringAfter("#") + + val dbSlug = request.url.toString() + .substringBefore("#") + .removeSuffix("/") + .substringAfterLast("/") + + val slugMap = getSlugMap().toMutableMap() + + // make sure db slug key is present in the slugMap + val storedSlug = slugMap[dbSlug] ?: dbSlug + + val response = chain.proceed(newRequest(frag, storedSlug)) + + if (!response.isSuccessful && response.code == 404) { + response.close() + + val newSlug = getNewSlug(storedSlug, search) + ?: throw IOException("Migrate from Asura to Asura") + + slugMap[dbSlug] = newSlug + putSlugMap(slugMap) + + return chain.proceed(newRequest(frag, newSlug)) + } + + return response } - private fun SManga.tempUrlToPermIfNeeded(): SManga { - val turnTempUrlToPerm = preferences.getBoolean(getPermanentMangaUrlPreferenceKey(), true) - if (!turnTempUrlToPerm) return this + private fun getNewSlug(existingSlug: String, search: String): String? { + val permaSlug = existingSlug + .replaceFirst(TEMP_TO_PERM_REGEX, "") - val sMangaTitleFirstWord = this.title.split(" ")[0] - if (!this.url.contains("/$sMangaTitleFirstWord", ignoreCase = true)) { - this.url = this.url.replaceFirst(TEMP_TO_PERM_URL_REGEX, "$1") - } - return this - } - - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val permanentMangaUrlPref = SwitchPreferenceCompat(screen.context).apply { - key = getPermanentMangaUrlPreferenceKey() - title = PREF_PERM_MANGA_URL_TITLE - summary = PREF_PERM_MANGA_URL_SUMMARY - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit() - .putBoolean(getPermanentMangaUrlPreferenceKey(), checkValue) - .commit() + val mangas = client.newCall(searchMangaRequest(1, search, FilterList())) + .execute() + .use { + searchMangaParse(it) } + + return mangas.mangas.firstOrNull { newManga -> + newManga.url.contains(permaSlug, true) + } + ?.url + ?.removeSuffix("/") + ?.substringAfterLast("/") + } + + private fun newRequest(frag: String, slug: String): Request { + val manga = SManga.create().apply { + this.url = "$mangaUrlDirectory/$slug/" } - screen.addPreference(permanentMangaUrlPref) - addRandomAndCustomUserAgentPreferences(screen) + return when (frag.substringBefore("#")) { + "chapters" -> chapterListRequest(manga) + "details" -> mangaDetailsRequest(manga) + else -> throw IOException("unknown url fragment for urlChangeInterceptor") + } } - private fun getPermanentMangaUrlPreferenceKey(): String { - return PREF_PERM_MANGA_URL_KEY_PREFIX + lang + private fun putSlugMap(slugMap: MutableMap) { + val serialized = json.encodeToString(slugMap) + + preferences.edit().putString(PREF_URL_MAP, serialized).commit() + } + + private fun getSlugMap(): Map { + val serialized = preferences.getString(PREF_URL_MAP, null) ?: return emptyMap() + + return try { + json.decodeFromString(serialized) + } catch (e: Exception) { + emptyMap() + } + } + + private fun String.toSearchQuery(): String { + return this.trim() + .lowercase() + .replace(titleSpecialCharactersRegex, "+") + .replace(trailingPlusRegex, "") } - // Permanent Url for Manga/Chapter End companion object { - private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_" - private const val PREF_PERM_MANGA_URL_TITLE = "Permanent Manga URL" - private const val PREF_PERM_MANGA_URL_SUMMARY = "Turns all manga urls into permanent ones." - - private val TEMP_TO_PERM_URL_REGEX = Regex("""(/)\d+-""") + private const val PREF_URL_MAP = "pref_url_map" + private val TEMP_TO_PERM_REGEX = Regex("""^\d+-""") + private val titleSpecialCharactersRegex = Regex("""[^a-z0-9]+""") + private val trailingPlusRegex = Regex("""\++$""") } } diff --git a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansFactory.kt b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansFactory.kt index 7b847e0d7..70feef773 100644 --- a/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansFactory.kt +++ b/multisrc/overrides/mangathemesia/asurascans/src/AsuraScansFactory.kt @@ -1,7 +1,11 @@ package eu.kanade.tachiyomi.extension.all.asurascans import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import org.jsoup.nodes.Document import java.text.SimpleDateFormat import java.util.Locale @@ -12,7 +16,7 @@ class AsuraScansFactory : SourceFactory { ) } -class AsuraScansEn : AsuraScans("https://asura.gg", "en", SimpleDateFormat("MMM d, yyyy", Locale.US)) { +class AsuraScansEn : AsuraScans("https://www.asurascans.com", "en", SimpleDateFormat("MMM d, yyyy", Locale.US)) { override val seriesDescriptionSelector = "div.desc p, div.entry-content p, div[itemprop=description]:not(:has(p))" @@ -35,4 +39,23 @@ class AsuraScansTr : AsuraScans("https://asurascanstr.com", "tr", SimpleDateForm this.contains("Tamamlandı", ignoreCase = true) -> SManga.COMPLETED else -> SManga.UNKNOWN } + + override fun pageListParse(document: Document): List { + val scriptContent = document.selectFirst("script:containsData(ts_reader)")!!.data() + val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");") + val tsReader = json.decodeFromString(jsonString) + val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList() + return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + } + + @Serializable + data class TSReader( + val sources: List, + ) + + @Serializable + data class ReaderImageSource( + val source: String, + val images: List, + ) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt index 57b3120ea..09accdfe9 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt @@ -14,7 +14,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 25 override val sources = listOf( - MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 20), + MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 21), MultiLang("Flame Scans", "https://flamescans.org", listOf("en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 4), MultiLang("Komik Lab", "https://komiklab.com", listOf("en", "id"), className = "KomikLabFactory", pkgName = "komiklab", overrideVersionCode = 1), MultiLang("Miau Scan", "https://miauscan.com", listOf("es", "pt-BR")),