Improve Luminous Scans Functionality (Permanent URLs) (#44)

This commit is contained in:
Liam Rooney 2024-01-10 04:26:01 -08:00 committed by GitHub
parent 69f97415b7
commit 2ef685d0b3
2 changed files with 226 additions and 1 deletions

View File

@ -1,10 +1,235 @@
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.network.interceptor.rateLimit
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://luminousscans.net", "en", mangaUrlDirectory = "/series") {
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
val url = request.url.newBuilder()
.addPathSegment("page/$page/")
.removeAllQueryParameters("page")
.removeAllQueryParameters("title")
.addQueryParameter("s", query)
.build()
return request.newBuilder()
.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)
super.setupPreferenceScreen(screen)
}
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("""\++$""")
}
}

View File

@ -75,7 +75,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
SingleLang("Legacy Scans", "https://legacy-scans.com", "fr", pkgName = "flamescansfr"),
SingleLang("Lelmanga", "https://www.lelmanga.com", "fr"),
SingleLang("LianScans", "https://www.lianscans.my.id", "id", isNsfw = true),
SingleLang("Luminous Scans", "https://luminousscans.net", "en"),
SingleLang("Luminous Scans", "https://luminousscans.net", "en", overrideVersionCode = 1),
SingleLang("Lunar Scans", "https://lunarscan.org", "en", isNsfw = true, overrideVersionCode = 1),
SingleLang("LynxScans", "https://lynxscans.com", "en"),
SingleLang("Lyra Scans", "https://lyrascans.com", "en"),