MangaThemesia: add class to handle dynamic urls in sources (#1793)
* MangaThemesia: add alternative class to handle dynamic urls * use MangaThemesiaAlt on Asura & Luminous * use MangaThemesiaAlt on Rizz * don't update in getMangaUrl * small cleanup * remove other pref as well LuminousScans * wording * remove from FlameComics, since they no longer appear to do it * review comments * lint * actual old pref key Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> * actual old pref key x2 Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> --------- Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									d7c2e7b9da
								
							
						
					
					
						commit
						0594d08440
					
				@ -0,0 +1,162 @@
 | 
			
		||||
package eu.kanade.tachiyomi.multisrc.mangathemesia
 | 
			
		||||
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
import androidx.preference.PreferenceScreen
 | 
			
		||||
import androidx.preference.SwitchPreferenceCompat
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.await
 | 
			
		||||
import eu.kanade.tachiyomi.source.ConfigurableSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.MangasPage
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
import kotlinx.coroutines.sync.Mutex
 | 
			
		||||
import kotlinx.coroutines.sync.withLock
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.lang.ref.SoftReference
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
abstract class MangaThemesiaAlt(
 | 
			
		||||
    name: String,
 | 
			
		||||
    baseUrl: String,
 | 
			
		||||
    lang: String,
 | 
			
		||||
    mangaUrlDirectory: String = "/manga",
 | 
			
		||||
    dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
 | 
			
		||||
    private val randomUrlPrefKey: String = "pref_auto_random_url",
 | 
			
		||||
) : MangaThemesia(name, baseUrl, lang, mangaUrlDirectory, dateFormat), ConfigurableSource {
 | 
			
		||||
 | 
			
		||||
    protected val preferences: SharedPreferences by lazy {
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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"
 | 
			
		||||
            setDefaultValue(true)
 | 
			
		||||
        }.also(screen::addPreference)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getRandomUrlPref() = preferences.getBoolean(randomUrlPrefKey, true)
 | 
			
		||||
 | 
			
		||||
    private var randomPartCache = SuspendLazy(::updateRandomPart)
 | 
			
		||||
 | 
			
		||||
    protected open fun getRandomPart(response: Response): String {
 | 
			
		||||
        return response.asJsoup()
 | 
			
		||||
            .selectFirst(searchMangaSelector())!!
 | 
			
		||||
            .select("a").attr("href")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
            .substringBefore("-")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected suspend fun updateRandomPart() =
 | 
			
		||||
        client.newCall(GET("$baseUrl$mangaUrlDirectory/", headers))
 | 
			
		||||
            .await()
 | 
			
		||||
            .use(::getRandomPart)
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        val mp = super.searchMangaParse(response)
 | 
			
		||||
 | 
			
		||||
        if (!getRandomUrlPref()) return mp
 | 
			
		||||
 | 
			
		||||
        val mangas = mp.mangas.toPermanentMangaUrls()
 | 
			
		||||
 | 
			
		||||
        return MangasPage(mangas, mp.hasNextPage)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected fun List<SManga>.toPermanentMangaUrls(): List<SManga> {
 | 
			
		||||
        for (i in indices) {
 | 
			
		||||
            val permaSlug = this[i].url
 | 
			
		||||
                .removeSuffix("/")
 | 
			
		||||
                .substringAfterLast("/")
 | 
			
		||||
                .replaceFirst(slugRegex, "")
 | 
			
		||||
 | 
			
		||||
            this[i].url = "$mangaUrlDirectory/$permaSlug/"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected open val slugRegex = Regex("""^\d+-""")
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsRequest(manga: SManga): Request {
 | 
			
		||||
        if (!getRandomUrlPref()) return super.mangaDetailsRequest(manga)
 | 
			
		||||
 | 
			
		||||
        val slug = manga.url
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
            .replaceFirst(slugRegex, "")
 | 
			
		||||
 | 
			
		||||
        val randomPart = randomPartCache.blockingGet()
 | 
			
		||||
 | 
			
		||||
        return GET("$baseUrl$mangaUrlDirectory/$randomPart-$slug/", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMangaUrl(manga: SManga): String {
 | 
			
		||||
        if (!getRandomUrlPref()) return super.getMangaUrl(manga)
 | 
			
		||||
 | 
			
		||||
        val slug = manga.url
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
            .replaceFirst(slugRegex, "")
 | 
			
		||||
 | 
			
		||||
        // we don't want to make network calls when user simply opens the entry
 | 
			
		||||
        val randomPart = randomPartCache.peek()?.let { "$it-" } ?: ""
 | 
			
		||||
 | 
			
		||||
        return "$baseUrl$mangaUrlDirectory/$randomPart$slug/"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class SuspendLazy<T : Any>(
 | 
			
		||||
    private val initializer: suspend () -> T,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val mutex = Mutex()
 | 
			
		||||
    private var cachedValue: SoftReference<T>? = null
 | 
			
		||||
    private var fetchTime = 0L
 | 
			
		||||
 | 
			
		||||
    suspend fun get(): T {
 | 
			
		||||
        if (fetchTime + 3600000 < System.currentTimeMillis()) {
 | 
			
		||||
            // reset cache
 | 
			
		||||
            cachedValue = null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // fast way
 | 
			
		||||
        cachedValue?.get()?.let {
 | 
			
		||||
            return it
 | 
			
		||||
        }
 | 
			
		||||
        return mutex.withLock {
 | 
			
		||||
            cachedValue?.get()?.let {
 | 
			
		||||
                return it
 | 
			
		||||
            }
 | 
			
		||||
            val result = initializer()
 | 
			
		||||
            cachedValue = SoftReference(result)
 | 
			
		||||
            fetchTime = System.currentTimeMillis()
 | 
			
		||||
 | 
			
		||||
            result
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun peek(): T? {
 | 
			
		||||
        return cachedValue?.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun blockingGet(): T {
 | 
			
		||||
        return runBlocking { get() }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,7 @@ ext {
 | 
			
		||||
    extClass = '.AsuraScans'
 | 
			
		||||
    themePkg = 'mangathemesia'
 | 
			
		||||
    baseUrl = 'https://asuratoon.com'
 | 
			
		||||
    overrideVersionCode = 1
 | 
			
		||||
    overrideVersionCode = 2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
 | 
			
		||||
@ -1,53 +1,35 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.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.multisrc.mangathemesia.MangaThemesiaAlt
 | 
			
		||||
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 rx.Observable
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
class AsuraScans :
 | 
			
		||||
    MangaThemesia(
 | 
			
		||||
        "Asura Scans",
 | 
			
		||||
        "https://asuratoon.com",
 | 
			
		||||
        "en",
 | 
			
		||||
        dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
 | 
			
		||||
    ),
 | 
			
		||||
    ConfigurableSource {
 | 
			
		||||
 | 
			
		||||
    private val preferences by lazy {
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
 | 
			
		||||
class AsuraScans : MangaThemesiaAlt(
 | 
			
		||||
    "Asura Scans",
 | 
			
		||||
    "https://asuratoon.com",
 | 
			
		||||
    "en",
 | 
			
		||||
    dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
 | 
			
		||||
    randomUrlPrefKey = "pref_permanent_manga_url_2_en",
 | 
			
		||||
) {
 | 
			
		||||
    init {
 | 
			
		||||
        // remove legacy preferences
 | 
			
		||||
        preferences.run {
 | 
			
		||||
            if (contains("pref_url_map")) {
 | 
			
		||||
                edit().remove("pref_url_map").apply()
 | 
			
		||||
            }
 | 
			
		||||
            if (contains("pref_base_url_host")) {
 | 
			
		||||
                edit().remove("pref_base_url_host").apply()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val baseUrl by lazy {
 | 
			
		||||
        preferences.baseUrlHost.let { "https://$it" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = super.client.newBuilder()
 | 
			
		||||
        .addInterceptor(::urlChangeInterceptor)
 | 
			
		||||
        .addInterceptor(::domainChangeIntercept)
 | 
			
		||||
        .rateLimit(1, 3, TimeUnit.SECONDS)
 | 
			
		||||
    override val client = super.client.newBuilder()
 | 
			
		||||
        .rateLimit(1, 3)
 | 
			
		||||
        .apply {
 | 
			
		||||
            val interceptors = interceptors()
 | 
			
		||||
            val index = interceptors.indexOfFirst { "Brotli" in it.javaClass.simpleName }
 | 
			
		||||
@ -64,19 +46,6 @@ class AsuraScans :
 | 
			
		||||
    override val pageSelector = "div.rdminimal > img, div.rdminimal > p > img, div.rdminimal > a > img, div.rdminimal > p > a > img, " +
 | 
			
		||||
        "div.rdminimal > noscript > img, div.rdminimal > p > noscript > img, div.rdminimal > a > noscript > img, div.rdminimal > p > a > noscript > img"
 | 
			
		||||
 | 
			
		||||
    // Permanent Url for Manga/Chapter End
 | 
			
		||||
    override fun fetchPopularManga(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchPopularManga(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val request = super.searchMangaRequest(page, query, filters)
 | 
			
		||||
        if (query.isBlank()) return request
 | 
			
		||||
@ -93,232 +62,10 @@ class AsuraScans :
 | 
			
		||||
            .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Temp Url for manga/chapter
 | 
			
		||||
    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
 | 
			
		||||
        val newManga = manga.titleToUrlFrag()
 | 
			
		||||
 | 
			
		||||
        return super.fetchChapterList(newManga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
 | 
			
		||||
        val newManga = manga.titleToUrlFrag()
 | 
			
		||||
 | 
			
		||||
        return super.fetchMangaDetails(newManga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMangaUrl(manga: SManga): String {
 | 
			
		||||
        val dbSlug = manga.url
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
        val storedSlug = preferences.slugMap[dbSlug] ?: dbSlug
 | 
			
		||||
 | 
			
		||||
        return "$baseUrl$mangaUrlDirectory/$storedSlug/"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Skip scriptPages
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select(pageSelector)
 | 
			
		||||
            .filterNot { it.attr("src").isNullOrEmpty() }
 | 
			
		||||
            .mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
 | 
			
		||||
        return this.map { mangasPage ->
 | 
			
		||||
            MangasPage(
 | 
			
		||||
                mangasPage.mangas.map { it.tempUrlToPermIfNeeded() },
 | 
			
		||||
                mangasPage.hasNextPage,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SManga.tempUrlToPermIfNeeded(): SManga {
 | 
			
		||||
        if (!preferences.permaUrlPref) return this
 | 
			
		||||
 | 
			
		||||
        val slugMap = preferences.slugMap
 | 
			
		||||
 | 
			
		||||
        val sMangaTitleFirstWord = this.title.split(" ")[0]
 | 
			
		||||
        if (!this.url.contains("/$sMangaTitleFirstWord", ignoreCase = true)) {
 | 
			
		||||
            val currentSlug = this.url
 | 
			
		||||
                .removeSuffix("/")
 | 
			
		||||
                .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
            val permaSlug = currentSlug.replaceFirst(TEMP_TO_PERM_REGEX, "")
 | 
			
		||||
 | 
			
		||||
            slugMap[permaSlug] = currentSlug
 | 
			
		||||
 | 
			
		||||
            this.url = "$mangaUrlDirectory/$permaSlug/"
 | 
			
		||||
        }
 | 
			
		||||
        preferences.slugMap = slugMap
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SManga.titleToUrlFrag(): SManga {
 | 
			
		||||
        return try {
 | 
			
		||||
            this.apply {
 | 
			
		||||
                url = "$url#${title.toSearchQuery()}"
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: UninitializedPropertyAccessException) {
 | 
			
		||||
            // when called from deep link, title is not present
 | 
			
		||||
            this
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun urlChangeInterceptor(chain: Interceptor.Chain): Response {
 | 
			
		||||
        val request = chain.request()
 | 
			
		||||
 | 
			
		||||
        val frag = request.url.fragment
 | 
			
		||||
 | 
			
		||||
        if (frag.isNullOrEmpty()) {
 | 
			
		||||
            return chain.proceed(request)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val dbSlug = request.url.toString()
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
        val slugMap = preferences.slugMap
 | 
			
		||||
 | 
			
		||||
        val storedSlug = slugMap[dbSlug] ?: dbSlug
 | 
			
		||||
 | 
			
		||||
        val response = chain.proceed(
 | 
			
		||||
            request.newBuilder()
 | 
			
		||||
                .url("$baseUrl$mangaUrlDirectory/$storedSlug/")
 | 
			
		||||
                .build(),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (!response.isSuccessful && response.code == 404) {
 | 
			
		||||
            response.close()
 | 
			
		||||
 | 
			
		||||
            val newSlug = getNewSlug(storedSlug, frag)
 | 
			
		||||
                ?: throw IOException("Migrate from Asura to Asura")
 | 
			
		||||
 | 
			
		||||
            slugMap[dbSlug] = newSlug
 | 
			
		||||
            preferences.slugMap = slugMap
 | 
			
		||||
 | 
			
		||||
            return chain.proceed(
 | 
			
		||||
                request.newBuilder()
 | 
			
		||||
                    .url("$baseUrl$mangaUrlDirectory/$newSlug/")
 | 
			
		||||
                    .build(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getNewSlug(existingSlug: String, frag: String): String? {
 | 
			
		||||
        val permaSlug = existingSlug
 | 
			
		||||
            .replaceFirst(TEMP_TO_PERM_REGEX, "")
 | 
			
		||||
 | 
			
		||||
        val search = frag.substringBefore("#")
 | 
			
		||||
 | 
			
		||||
        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 String.toSearchQuery(): String {
 | 
			
		||||
        return this.trim()
 | 
			
		||||
            .lowercase()
 | 
			
		||||
            .replace(titleSpecialCharactersRegex, "+")
 | 
			
		||||
            .replace(trailingPlusRegex, "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var lastDomain = ""
 | 
			
		||||
 | 
			
		||||
    private fun domainChangeIntercept(chain: Interceptor.Chain): Response {
 | 
			
		||||
        val request = chain.request()
 | 
			
		||||
 | 
			
		||||
        if (request.url.host !in listOf(preferences.baseUrlHost, lastDomain)) {
 | 
			
		||||
            return chain.proceed(request)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (lastDomain.isNotEmpty()) {
 | 
			
		||||
            val newUrl = request.url.newBuilder()
 | 
			
		||||
                .host(preferences.baseUrlHost)
 | 
			
		||||
                .build()
 | 
			
		||||
 | 
			
		||||
            return chain.proceed(
 | 
			
		||||
                request.newBuilder()
 | 
			
		||||
                    .url(newUrl)
 | 
			
		||||
                    .build(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val response = chain.proceed(request)
 | 
			
		||||
 | 
			
		||||
        if (request.url.host == response.request.url.host) return response
 | 
			
		||||
 | 
			
		||||
        response.close()
 | 
			
		||||
 | 
			
		||||
        preferences.baseUrlHost = response.request.url.host
 | 
			
		||||
 | 
			
		||||
        lastDomain = request.url.host
 | 
			
		||||
 | 
			
		||||
        val newUrl = request.url.newBuilder()
 | 
			
		||||
            .host(response.request.url.host)
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
        return chain.proceed(
 | 
			
		||||
            request.newBuilder()
 | 
			
		||||
                .url(newUrl)
 | 
			
		||||
                .build(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setupPreferenceScreen(screen: PreferenceScreen) {
 | 
			
		||||
        SwitchPreferenceCompat(screen.context).apply {
 | 
			
		||||
            key = PREF_PERM_MANGA_URL_KEY_PREFIX + lang
 | 
			
		||||
            title = PREF_PERM_MANGA_URL_TITLE
 | 
			
		||||
            summary = PREF_PERM_MANGA_URL_SUMMARY
 | 
			
		||||
            setDefaultValue(true)
 | 
			
		||||
        }.also(screen::addPreference)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val SharedPreferences.permaUrlPref
 | 
			
		||||
        get() = getBoolean(PREF_PERM_MANGA_URL_KEY_PREFIX + lang, true)
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.slugMap: MutableMap<String, String>
 | 
			
		||||
        get() {
 | 
			
		||||
            val serialized = getString(PREF_URL_MAP, null) ?: return mutableMapOf()
 | 
			
		||||
 | 
			
		||||
            return try {
 | 
			
		||||
                json.decodeFromString(serialized)
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                mutableMapOf()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        set(slugMap) {
 | 
			
		||||
            val serialized = json.encodeToString(slugMap)
 | 
			
		||||
            edit().putString(PREF_URL_MAP, serialized).commit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.baseUrlHost
 | 
			
		||||
        get() = getString(BASE_URL_PREF, defaultBaseUrlHost) ?: defaultBaseUrlHost
 | 
			
		||||
        set(newHost) {
 | 
			
		||||
            edit().putString(BASE_URL_PREF, newHost).commit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_2_"
 | 
			
		||||
        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 const val PREF_URL_MAP = "pref_url_map"
 | 
			
		||||
        private const val BASE_URL_PREF = "pref_base_url_host"
 | 
			
		||||
        private const val defaultBaseUrlHost = "asuratoon.com"
 | 
			
		||||
        private val TEMP_TO_PERM_REGEX = Regex("""^\d+-""")
 | 
			
		||||
        private val titleSpecialCharactersRegex = Regex("""[^a-z0-9]+""")
 | 
			
		||||
        private val trailingPlusRegex = Regex("""\++$""")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ ext {
 | 
			
		||||
    extClass = '.FlameComics'
 | 
			
		||||
    themePkg = 'mangathemesia'
 | 
			
		||||
    baseUrl = 'https://flamecomics.com'
 | 
			
		||||
    overrideVersionCode = 0
 | 
			
		||||
    overrideVersionCode = 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
 | 
			
		||||
@ -1,47 +1,30 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.flamecomics
 | 
			
		||||
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.graphics.Bitmap
 | 
			
		||||
import android.graphics.BitmapFactory
 | 
			
		||||
import android.graphics.Canvas
 | 
			
		||||
import android.graphics.Rect
 | 
			
		||||
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 okhttp3.Interceptor
 | 
			
		||||
import okhttp3.MediaType.Companion.toMediaType
 | 
			
		||||
import okhttp3.Protocol
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import okhttp3.ResponseBody.Companion.toResponseBody
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.io.ByteArrayOutputStream
 | 
			
		||||
 | 
			
		||||
class FlameComics :
 | 
			
		||||
    MangaThemesia(
 | 
			
		||||
        "Flame Comics",
 | 
			
		||||
        "https://flamecomics.com",
 | 
			
		||||
        "en",
 | 
			
		||||
        mangaUrlDirectory = "/series",
 | 
			
		||||
    ),
 | 
			
		||||
    ConfigurableSource {
 | 
			
		||||
class FlameComics : MangaThemesia(
 | 
			
		||||
    "Flame Comics",
 | 
			
		||||
    "https://flamecomics.com",
 | 
			
		||||
    "en",
 | 
			
		||||
    mangaUrlDirectory = "/series",
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    // Flame Scans -> Flame Comics
 | 
			
		||||
    override val id = 6350607071566689772
 | 
			
		||||
 | 
			
		||||
    private val preferences by lazy {
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val client = super.client.newBuilder()
 | 
			
		||||
        .rateLimit(2, 7)
 | 
			
		||||
        .addInterceptor(::composedImageIntercept)
 | 
			
		||||
@ -130,114 +113,8 @@ class FlameComics :
 | 
			
		||||
    }
 | 
			
		||||
    // Split Image Fixer End
 | 
			
		||||
 | 
			
		||||
    // Permanent Url start
 | 
			
		||||
    override fun fetchPopularManga(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchPopularManga(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
 | 
			
		||||
        return this.map { mangasPage ->
 | 
			
		||||
            MangasPage(
 | 
			
		||||
                mangasPage.mangas.map { it.tempUrlToPermIfNeeded() },
 | 
			
		||||
                mangasPage.hasNextPage,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SManga.tempUrlToPermIfNeeded(): SManga {
 | 
			
		||||
        val turnTempUrlToPerm = preferences.getBoolean(getPermanentMangaUrlPreferenceKey(), true)
 | 
			
		||||
        if (!turnTempUrlToPerm) return this
 | 
			
		||||
 | 
			
		||||
        val path = this.url.removePrefix("/").removeSuffix("/").split("/")
 | 
			
		||||
        path.lastOrNull()?.let { slug -> this.url = "$mangaUrlDirectory/${deobfuscateSlug(slug)}/" }
 | 
			
		||||
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchChapterList(manga: SManga) = super.fetchChapterList(manga.tempUrlToPermIfNeeded())
 | 
			
		||||
        .map { sChapterList -> sChapterList.map { it.tempUrlToPermIfNeeded() } }
 | 
			
		||||
 | 
			
		||||
    private fun SChapter.tempUrlToPermIfNeeded(): SChapter {
 | 
			
		||||
        val turnTempUrlToPerm = preferences.getBoolean(getPermanentChapterUrlPreferenceKey(), true)
 | 
			
		||||
        if (!turnTempUrlToPerm) return this
 | 
			
		||||
 | 
			
		||||
        val path = this.url.removePrefix("/").removeSuffix("/").split("/")
 | 
			
		||||
        path.lastOrNull()?.let { slug -> this.url = "/${deobfuscateSlug(slug)}/" }
 | 
			
		||||
        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 permanentChapterUrlPref = SwitchPreferenceCompat(screen.context).apply {
 | 
			
		||||
            key = getPermanentChapterUrlPreferenceKey()
 | 
			
		||||
            title = PREF_PERM_CHAPTER_URL_TITLE
 | 
			
		||||
            summary = PREF_PERM_CHAPTER_URL_SUMMARY
 | 
			
		||||
            setDefaultValue(true)
 | 
			
		||||
 | 
			
		||||
            setOnPreferenceChangeListener { _, newValue ->
 | 
			
		||||
                val checkValue = newValue as Boolean
 | 
			
		||||
                preferences.edit()
 | 
			
		||||
                    .putBoolean(getPermanentChapterUrlPreferenceKey(), checkValue)
 | 
			
		||||
                    .commit()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        screen.addPreference(permanentMangaUrlPref)
 | 
			
		||||
        screen.addPreference(permanentChapterUrlPref)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getPermanentMangaUrlPreferenceKey(): String {
 | 
			
		||||
        return PREF_PERM_MANGA_URL_KEY_PREFIX + lang
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getPermanentChapterUrlPreferenceKey(): String {
 | 
			
		||||
        return PREF_PERM_CHAPTER_URL_KEY_PREFIX + lang
 | 
			
		||||
    }
 | 
			
		||||
    // Permanent Url for Manga/Chapter End
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val COMPOSED_SUFFIX = "?comp"
 | 
			
		||||
 | 
			
		||||
        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 const val PREF_PERM_CHAPTER_URL_KEY_PREFIX = "pref_permanent_chapter_url"
 | 
			
		||||
        private const val PREF_PERM_CHAPTER_URL_TITLE = "Permanent Chapter URL"
 | 
			
		||||
        private const val PREF_PERM_CHAPTER_URL_SUMMARY = "Turns all chapter urls into permanent ones."
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         *
 | 
			
		||||
         * De-obfuscates the slug of a series or chapter to the permanent slug
 | 
			
		||||
         *   * For a series: "12345678-this-is-a-series" -> "this-is-a-series"
 | 
			
		||||
         *   * For a chapter: "12345678-this-is-a-series-chapter-1" -> "this-is-a-series-chapter-1"
 | 
			
		||||
         *
 | 
			
		||||
         * @param obfuscated_slug the obfuscated slug of a series or chapter
 | 
			
		||||
         *
 | 
			
		||||
         * @return
 | 
			
		||||
         */
 | 
			
		||||
        private fun deobfuscateSlug(obfuscated_slug: String) = obfuscated_slug
 | 
			
		||||
            .replaceFirst(Regex("""^\d+-"""), "")
 | 
			
		||||
 | 
			
		||||
        private val MEDIA_TYPE = "image/png".toMediaType()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ ext {
 | 
			
		||||
    extClass = '.LuminousScans'
 | 
			
		||||
    themePkg = 'mangathemesia'
 | 
			
		||||
    baseUrl = 'https://lumitoon.com'
 | 
			
		||||
    overrideVersionCode = 3
 | 
			
		||||
    overrideVersionCode = 4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
 | 
			
		||||
@ -1,57 +1,30 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.luminousscans
 | 
			
		||||
 | 
			
		||||
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.multisrc.mangathemesia.MangaThemesiaAlt
 | 
			
		||||
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.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import kotlinx.serialization.decodeFromString
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
 | 
			
		||||
class LuminousScans :
 | 
			
		||||
    MangaThemesia(
 | 
			
		||||
        "Luminous Scans",
 | 
			
		||||
        "https://lumitoon.com",
 | 
			
		||||
        "en",
 | 
			
		||||
        mangaUrlDirectory = "/series",
 | 
			
		||||
    ),
 | 
			
		||||
    ConfigurableSource {
 | 
			
		||||
class LuminousScans : MangaThemesiaAlt(
 | 
			
		||||
    "Luminous Scans",
 | 
			
		||||
    "https://lumitoon.com",
 | 
			
		||||
    "en",
 | 
			
		||||
    mangaUrlDirectory = "/series",
 | 
			
		||||
    randomUrlPrefKey = "pref_permanent_manga_url_2_en",
 | 
			
		||||
) {
 | 
			
		||||
    init {
 | 
			
		||||
        // remove legacy preferences
 | 
			
		||||
        preferences.run {
 | 
			
		||||
            if (contains("pref_url_map")) {
 | 
			
		||||
                edit().remove("pref_url_map").apply()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val client = super.client.newBuilder()
 | 
			
		||||
        .addInterceptor(::urlChangeInterceptor)
 | 
			
		||||
        .rateLimit(2)
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    private val preferences by lazy {
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Permanent Url for Manga/Chapter End
 | 
			
		||||
    override fun fetchPopularManga(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchPopularManga(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchLatestUpdates(page).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
 | 
			
		||||
        return super.fetchSearchManga(page, query, filters).tempUrlToPermIfNeeded()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val request = super.searchMangaRequest(page, query, filters)
 | 
			
		||||
        if (query.isBlank()) return request
 | 
			
		||||
@ -67,176 +40,4 @@ class LuminousScans :
 | 
			
		||||
            .url(url)
 | 
			
		||||
            .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Temp Url for manga/chapter
 | 
			
		||||
    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
 | 
			
		||||
        val newManga = manga.titleToUrlFrag()
 | 
			
		||||
 | 
			
		||||
        return super.fetchChapterList(newManga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
 | 
			
		||||
        val newManga = manga.titleToUrlFrag()
 | 
			
		||||
 | 
			
		||||
        return super.fetchMangaDetails(newManga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMangaUrl(manga: SManga): String {
 | 
			
		||||
        val dbSlug = manga.url
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
        val storedSlug = preferences.slugMap[dbSlug] ?: dbSlug
 | 
			
		||||
 | 
			
		||||
        return "$baseUrl$mangaUrlDirectory/$storedSlug/"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
 | 
			
		||||
        return this.map { mangasPage ->
 | 
			
		||||
            MangasPage(
 | 
			
		||||
                mangasPage.mangas.map { it.tempUrlToPermIfNeeded() },
 | 
			
		||||
                mangasPage.hasNextPage,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SManga.tempUrlToPermIfNeeded(): SManga {
 | 
			
		||||
        if (!preferences.permaUrlPref) return this
 | 
			
		||||
 | 
			
		||||
        val slugMap = preferences.slugMap
 | 
			
		||||
 | 
			
		||||
        val sMangaTitleFirstWord = this.title.split(" ")[0]
 | 
			
		||||
        if (!this.url.contains("/$sMangaTitleFirstWord", ignoreCase = true)) {
 | 
			
		||||
            val currentSlug = this.url
 | 
			
		||||
                .removeSuffix("/")
 | 
			
		||||
                .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
            val permaSlug = currentSlug.replaceFirst(TEMP_TO_PERM_REGEX, "")
 | 
			
		||||
 | 
			
		||||
            slugMap[permaSlug] = currentSlug
 | 
			
		||||
 | 
			
		||||
            this.url = "$mangaUrlDirectory/$permaSlug/"
 | 
			
		||||
        }
 | 
			
		||||
        preferences.slugMap = slugMap
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SManga.titleToUrlFrag(): SManga {
 | 
			
		||||
        return try {
 | 
			
		||||
            this.apply {
 | 
			
		||||
                url = "$url#${title.toSearchQuery()}"
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: UninitializedPropertyAccessException) {
 | 
			
		||||
            // when called from deep link, title is not present
 | 
			
		||||
            this
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun urlChangeInterceptor(chain: Interceptor.Chain): Response {
 | 
			
		||||
        val request = chain.request()
 | 
			
		||||
 | 
			
		||||
        val frag = request.url.fragment
 | 
			
		||||
 | 
			
		||||
        if (frag.isNullOrEmpty()) {
 | 
			
		||||
            return chain.proceed(request)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val dbSlug = request.url.toString()
 | 
			
		||||
            .substringBefore("#")
 | 
			
		||||
            .removeSuffix("/")
 | 
			
		||||
            .substringAfterLast("/")
 | 
			
		||||
 | 
			
		||||
        val slugMap = preferences.slugMap
 | 
			
		||||
 | 
			
		||||
        val storedSlug = slugMap[dbSlug] ?: dbSlug
 | 
			
		||||
 | 
			
		||||
        val response = chain.proceed(
 | 
			
		||||
            request.newBuilder()
 | 
			
		||||
                .url("$baseUrl$mangaUrlDirectory/$storedSlug/")
 | 
			
		||||
                .build(),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (!response.isSuccessful && response.code == 404) {
 | 
			
		||||
            response.close()
 | 
			
		||||
 | 
			
		||||
            val newSlug = getNewSlug(storedSlug, frag)
 | 
			
		||||
                ?: throw IOException("Migrate from Luminous to Luminous")
 | 
			
		||||
 | 
			
		||||
            slugMap[dbSlug] = newSlug
 | 
			
		||||
            preferences.slugMap = slugMap
 | 
			
		||||
 | 
			
		||||
            return chain.proceed(
 | 
			
		||||
                request.newBuilder()
 | 
			
		||||
                    .url("$baseUrl$mangaUrlDirectory/$newSlug/")
 | 
			
		||||
                    .build(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getNewSlug(existingSlug: String, frag: String): String? {
 | 
			
		||||
        val permaSlug = existingSlug
 | 
			
		||||
            .replaceFirst(TEMP_TO_PERM_REGEX, "")
 | 
			
		||||
 | 
			
		||||
        val search = frag.substringBefore("#")
 | 
			
		||||
 | 
			
		||||
        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 String.toSearchQuery(): String {
 | 
			
		||||
        return this.trim()
 | 
			
		||||
            .lowercase()
 | 
			
		||||
            .replace(titleSpecialCharactersRegex, "+")
 | 
			
		||||
            .replace(trailingPlusRegex, "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setupPreferenceScreen(screen: PreferenceScreen) {
 | 
			
		||||
        SwitchPreferenceCompat(screen.context).apply {
 | 
			
		||||
            key = PREF_PERM_MANGA_URL_KEY_PREFIX + lang
 | 
			
		||||
            title = PREF_PERM_MANGA_URL_TITLE
 | 
			
		||||
            summary = PREF_PERM_MANGA_URL_SUMMARY
 | 
			
		||||
            setDefaultValue(true)
 | 
			
		||||
        }.also(screen::addPreference)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val SharedPreferences.permaUrlPref
 | 
			
		||||
        get() = getBoolean(PREF_PERM_MANGA_URL_KEY_PREFIX + lang, true)
 | 
			
		||||
 | 
			
		||||
    private var SharedPreferences.slugMap: MutableMap<String, String>
 | 
			
		||||
        get() {
 | 
			
		||||
            val serialized = getString(PREF_URL_MAP, null) ?: return mutableMapOf()
 | 
			
		||||
 | 
			
		||||
            return try {
 | 
			
		||||
                json.decodeFromString(serialized)
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                mutableMapOf()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        set(slugMap) {
 | 
			
		||||
            val serialized = json.encodeToString(slugMap)
 | 
			
		||||
            edit().putString(PREF_URL_MAP, serialized).commit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val PREF_PERM_MANGA_URL_KEY_PREFIX = "pref_permanent_manga_url_2_"
 | 
			
		||||
        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 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("""\++$""")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ ext {
 | 
			
		||||
    extClass = '.RizzComic'
 | 
			
		||||
    themePkg = 'mangathemesia'
 | 
			
		||||
    baseUrl = 'https://rizzcomic.com'
 | 
			
		||||
    overrideVersionCode = 0
 | 
			
		||||
    overrideVersionCode = 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
 | 
			
		||||
@ -26,53 +26,49 @@ abstract class SelectFilter(
 | 
			
		||||
class SortFilter(defaultOrder: String? = null) : SelectFilter("Sort By", sort, defaultOrder) {
 | 
			
		||||
    override val formParameter = "OrderValue"
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val sort = listOf(
 | 
			
		||||
            Pair("Default", "all"),
 | 
			
		||||
            Pair("A-Z", "title"),
 | 
			
		||||
            Pair("Z-A", "titlereverse"),
 | 
			
		||||
            Pair("Latest Update", "update"),
 | 
			
		||||
            Pair("Latest Added", "latest"),
 | 
			
		||||
            Pair("Popular", "popular"),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val POPULAR = FilterList(StatusFilter(), TypeFilter(), SortFilter("popular"))
 | 
			
		||||
        val LATEST = FilterList(StatusFilter(), TypeFilter(), SortFilter("update"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val sort = listOf(
 | 
			
		||||
    Pair("Default", "all"),
 | 
			
		||||
    Pair("A-Z", "title"),
 | 
			
		||||
    Pair("Z-A", "titlereverse"),
 | 
			
		||||
    Pair("Latest Update", "update"),
 | 
			
		||||
    Pair("Latest Added", "latest"),
 | 
			
		||||
    Pair("Popular", "popular"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class StatusFilter : SelectFilter("Status", status) {
 | 
			
		||||
    override val formParameter = "StatusValue"
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val status = listOf(
 | 
			
		||||
            Pair("All", "all"),
 | 
			
		||||
            Pair("Ongoing", "ongoing"),
 | 
			
		||||
            Pair("Complete", "completed"),
 | 
			
		||||
            Pair("Hiatus", "hiatus"),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val status = listOf(
 | 
			
		||||
    Pair("All", "all"),
 | 
			
		||||
    Pair("Ongoing", "ongoing"),
 | 
			
		||||
    Pair("Complete", "completed"),
 | 
			
		||||
    Pair("Hiatus", "hiatus"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class TypeFilter : SelectFilter("Type", type) {
 | 
			
		||||
    override val formParameter = "TypeValue"
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val type = listOf(
 | 
			
		||||
            Pair("All", "all"),
 | 
			
		||||
            Pair("Manga", "Manga"),
 | 
			
		||||
            Pair("Manhwa", "Manhwa"),
 | 
			
		||||
            Pair("Manhua", "Manhua"),
 | 
			
		||||
            Pair("Comic", "Comic"),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val type = listOf(
 | 
			
		||||
    Pair("All", "all"),
 | 
			
		||||
    Pair("Manga", "Manga"),
 | 
			
		||||
    Pair("Manhwa", "Manhwa"),
 | 
			
		||||
    Pair("Manhua", "Manhua"),
 | 
			
		||||
    Pair("Comic", "Comic"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class CheckBoxFilter(
 | 
			
		||||
    name: String,
 | 
			
		||||
    val value: String,
 | 
			
		||||
) : Filter.CheckBox(name)
 | 
			
		||||
 | 
			
		||||
class GenreFilter(
 | 
			
		||||
    genres: List<Pair<String, String>>,
 | 
			
		||||
) : FormBodyFilter, Filter.Group<CheckBoxFilter>(
 | 
			
		||||
class GenreFilter : FormBodyFilter, Filter.Group<CheckBoxFilter>(
 | 
			
		||||
    "Genre",
 | 
			
		||||
    genres.map { CheckBoxFilter(it.first, it.second) },
 | 
			
		||||
) {
 | 
			
		||||
@ -82,3 +78,34 @@ class GenreFilter(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val genres = listOf(
 | 
			
		||||
    Pair("Abilities", "2"),
 | 
			
		||||
    Pair("Action", "3"),
 | 
			
		||||
    Pair("Adaptation", "4"),
 | 
			
		||||
    Pair("Adventure", "5"),
 | 
			
		||||
    Pair("Another Chance", "6"),
 | 
			
		||||
    Pair("Apocalypse", "7"),
 | 
			
		||||
    Pair("Based On A Novel", "8"),
 | 
			
		||||
    Pair("Cheat", "9"),
 | 
			
		||||
    Pair("Comedy", "10"),
 | 
			
		||||
    Pair("Conspiracy", "11"),
 | 
			
		||||
    Pair("Cultivation", "12"),
 | 
			
		||||
    Pair("Demon", "13"),
 | 
			
		||||
    Pair("Demon King", "14"),
 | 
			
		||||
    Pair("Dragon", "15"),
 | 
			
		||||
    Pair("Drama", "16"),
 | 
			
		||||
    Pair("Drop", "17"),
 | 
			
		||||
    Pair("Dungeon", "18"),
 | 
			
		||||
    Pair("Dungeons", "19"),
 | 
			
		||||
    Pair("Fantasy", "20"),
 | 
			
		||||
    Pair("Game", "21"),
 | 
			
		||||
    Pair("Genius", "22"),
 | 
			
		||||
    Pair("Ghosts", "23"),
 | 
			
		||||
    Pair("Harem", "24"),
 | 
			
		||||
    Pair("Hero", "25"),
 | 
			
		||||
    Pair("Hidden Identity", "26"),
 | 
			
		||||
    Pair("HighFantasy", "27"),
 | 
			
		||||
    Pair("Historical", "28"),
 | 
			
		||||
    Pair("Horror", "29"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.en.rizzcomic
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
 | 
			
		||||
import androidx.preference.PreferenceScreen
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservableSuccess
 | 
			
		||||
@ -9,9 +10,7 @@ import eu.kanade.tachiyomi.source.model.Filter
 | 
			
		||||
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 eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.decodeFromString
 | 
			
		||||
@ -22,7 +21,7 @@ import rx.Observable
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
class RizzComic : MangaThemesia(
 | 
			
		||||
class RizzComic : MangaThemesiaAlt(
 | 
			
		||||
    "Rizz Comic",
 | 
			
		||||
    "https://rizzcomic.com",
 | 
			
		||||
    "en",
 | 
			
		||||
@ -40,43 +39,12 @@ class RizzComic : MangaThemesia(
 | 
			
		||||
            .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var urlPrefix: String? = null
 | 
			
		||||
    private var genreCache: List<Pair<String, String>> = emptyList()
 | 
			
		||||
    private var attempts = 0
 | 
			
		||||
    override val versionId = 2
 | 
			
		||||
 | 
			
		||||
    private fun updateCache() {
 | 
			
		||||
        if ((urlPrefix.isNullOrEmpty() || genreCache.isEmpty()) && attempts < 3) {
 | 
			
		||||
            runCatching {
 | 
			
		||||
                val document = client.newCall(GET("$baseUrl$mangaUrlDirectory", headers))
 | 
			
		||||
                    .execute().use { it.asJsoup() }
 | 
			
		||||
    override val slugRegex = Regex("""^r\d+-""")
 | 
			
		||||
 | 
			
		||||
                urlPrefix = document.selectFirst(".listupd a")
 | 
			
		||||
                    ?.attr("href")
 | 
			
		||||
                    ?.substringAfter("$mangaUrlDirectory/")
 | 
			
		||||
                    ?.substringBefore("-")
 | 
			
		||||
 | 
			
		||||
                genreCache = document.selectFirst(".filter .genrez")
 | 
			
		||||
                    ?.select("li")
 | 
			
		||||
                    .orEmpty()
 | 
			
		||||
                    .map {
 | 
			
		||||
                        val name = it.select("label").text()
 | 
			
		||||
                        val id = it.select("input").attr("value")
 | 
			
		||||
 | 
			
		||||
                        Pair(name, id)
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            attempts++
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getUrlPrefix(): String {
 | 
			
		||||
        if (urlPrefix.isNullOrEmpty()) {
 | 
			
		||||
            updateCache()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return urlPrefix ?: throw Exception("Unable to update dynamic urls")
 | 
			
		||||
    }
 | 
			
		||||
    // don't allow disabling random part setting
 | 
			
		||||
    override fun setupPreferenceScreen(screen: PreferenceScreen) { }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
 | 
			
		||||
    override fun popularMangaParse(response: Response) = searchMangaParse(response)
 | 
			
		||||
@ -103,30 +71,17 @@ class RizzComic : MangaThemesia(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getFilterList(): FilterList {
 | 
			
		||||
        val filters: MutableList<Filter<*>> = mutableListOf(
 | 
			
		||||
        return FilterList(
 | 
			
		||||
            Filter.Header("Filters don't work with text search"),
 | 
			
		||||
            SortFilter(),
 | 
			
		||||
            StatusFilter(),
 | 
			
		||||
            TypeFilter(),
 | 
			
		||||
            GenreFilter(),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        filters += if (genreCache.isEmpty()) {
 | 
			
		||||
            listOf(
 | 
			
		||||
                Filter.Separator(),
 | 
			
		||||
                Filter.Header("Press reset to attempt to load genres"),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            listOf(
 | 
			
		||||
                GenreFilter(genreCache),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return FilterList(filters)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class Comic(
 | 
			
		||||
        val id: Int,
 | 
			
		||||
        val title: String,
 | 
			
		||||
        @SerialName("image_url") val cover: String? = null,
 | 
			
		||||
        @SerialName("long_description") val synopsis: String? = null,
 | 
			
		||||
@ -150,13 +105,11 @@ class RizzComic : MangaThemesia(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaParse(response: Response): MangasPage {
 | 
			
		||||
        updateCache()
 | 
			
		||||
 | 
			
		||||
        val result = response.parseAs<List<Comic>>()
 | 
			
		||||
 | 
			
		||||
        val entries = result.map { comic ->
 | 
			
		||||
            SManga.create().apply {
 | 
			
		||||
                url = "${comic.slug}#${comic.id}"
 | 
			
		||||
                url = "$mangaUrlDirectory/${comic.slug}/"
 | 
			
		||||
                title = comic.title
 | 
			
		||||
                description = comic.synopsis
 | 
			
		||||
                author = listOfNotNull(comic.author, comic.serialization).joinToString()
 | 
			
		||||
@ -166,7 +119,7 @@ class RizzComic : MangaThemesia(
 | 
			
		||||
                genre = buildList {
 | 
			
		||||
                    add(comic.type?.capitalize())
 | 
			
		||||
                    comic.genreIds?.onEach { gId ->
 | 
			
		||||
                        add(genreCache.firstOrNull { it.second == gId }?.first)
 | 
			
		||||
                        add(genres.firstOrNull { it.second == gId }?.first)
 | 
			
		||||
                    }
 | 
			
		||||
                }.filterNotNull().joinToString()
 | 
			
		||||
                initialized = true
 | 
			
		||||
@ -182,37 +135,6 @@ class RizzComic : MangaThemesia(
 | 
			
		||||
            .map { mangaDetailsParse(it).apply { description = manga.description } }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsRequest(manga: SManga): Request {
 | 
			
		||||
        val slug = manga.url.substringBefore("#")
 | 
			
		||||
        val randomPart = getUrlPrefix()
 | 
			
		||||
 | 
			
		||||
        return GET("$baseUrl/series/$randomPart-$slug", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMangaUrl(manga: SManga): String {
 | 
			
		||||
        val slug = manga.url.substringBefore("#")
 | 
			
		||||
 | 
			
		||||
        val urlPart = urlPrefix?.let { "$it-" } ?: ""
 | 
			
		||||
 | 
			
		||||
        return "$baseUrl/series/$urlPart$slug"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        return super.chapterListParse(response).map { chapter ->
 | 
			
		||||
            chapter.apply {
 | 
			
		||||
                url = url.removeSuffix("/")
 | 
			
		||||
                    .substringAfter("/")
 | 
			
		||||
                    .substringAfter("-")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pageListRequest(chapter: SChapter): Request {
 | 
			
		||||
        return GET("$baseUrl/chapter/${getUrlPrefix()}-${chapter.url}", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageRequest(page: Page): Request {
 | 
			
		||||
        val newHeaders = headersBuilder()
 | 
			
		||||
            .set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*")
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user