mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-25 11:42:47 +01:00
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
cc6e67f0e2
commit
d3f33327c2
@ -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'
|
extClass = '.AsuraScans'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://asuratoon.com'
|
baseUrl = 'https://asuratoon.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,53 +1,35 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.asurascans
|
package eu.kanade.tachiyomi.extension.en.asurascans
|
||||||
|
|
||||||
import android.app.Application
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt
|
||||||
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.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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.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.Request
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
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.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class AsuraScans :
|
class AsuraScans : MangaThemesiaAlt(
|
||||||
MangaThemesia(
|
"Asura Scans",
|
||||||
"Asura Scans",
|
"https://asuratoon.com",
|
||||||
"https://asuratoon.com",
|
"en",
|
||||||
"en",
|
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
|
||||||
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
|
randomUrlPrefKey = "pref_permanent_manga_url_2_en",
|
||||||
),
|
) {
|
||||||
ConfigurableSource {
|
init {
|
||||||
|
// remove legacy preferences
|
||||||
private val preferences by lazy {
|
preferences.run {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
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 {
|
override val client = super.client.newBuilder()
|
||||||
preferences.baseUrlHost.let { "https://$it" }
|
.rateLimit(1, 3)
|
||||||
}
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.addInterceptor(::urlChangeInterceptor)
|
|
||||||
.addInterceptor(::domainChangeIntercept)
|
|
||||||
.rateLimit(1, 3, TimeUnit.SECONDS)
|
|
||||||
.apply {
|
.apply {
|
||||||
val interceptors = interceptors()
|
val interceptors = interceptors()
|
||||||
val index = interceptors.indexOfFirst { "Brotli" in it.javaClass.simpleName }
|
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, " +
|
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"
|
"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 {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val request = super.searchMangaRequest(page, query, filters)
|
val request = super.searchMangaRequest(page, query, filters)
|
||||||
if (query.isBlank()) return request
|
if (query.isBlank()) return request
|
||||||
@ -93,232 +62,10 @@ class AsuraScans :
|
|||||||
.build()
|
.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
|
// Skip scriptPages
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
return document.select(pageSelector)
|
return document.select(pageSelector)
|
||||||
.filterNot { it.attr("src").isNullOrEmpty() }
|
.filterNot { it.attr("src").isNullOrEmpty() }
|
||||||
.mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) }
|
.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'
|
extClass = '.FlameComics'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://flamecomics.com'
|
baseUrl = 'https://flamecomics.com'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,47 +1,30 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.flamecomics
|
package eu.kanade.tachiyomi.extension.en.flamecomics
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
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.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.Protocol
|
import okhttp3.Protocol
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
class FlameComics :
|
class FlameComics : MangaThemesia(
|
||||||
MangaThemesia(
|
"Flame Comics",
|
||||||
"Flame Comics",
|
"https://flamecomics.com",
|
||||||
"https://flamecomics.com",
|
"en",
|
||||||
"en",
|
mangaUrlDirectory = "/series",
|
||||||
mangaUrlDirectory = "/series",
|
) {
|
||||||
),
|
|
||||||
ConfigurableSource {
|
|
||||||
|
|
||||||
// Flame Scans -> Flame Comics
|
// Flame Scans -> Flame Comics
|
||||||
override val id = 6350607071566689772
|
override val id = 6350607071566689772
|
||||||
|
|
||||||
private val preferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val client = super.client.newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
.rateLimit(2, 7)
|
.rateLimit(2, 7)
|
||||||
.addInterceptor(::composedImageIntercept)
|
.addInterceptor(::composedImageIntercept)
|
||||||
@ -130,114 +113,8 @@ class FlameComics :
|
|||||||
}
|
}
|
||||||
// Split Image Fixer End
|
// 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 {
|
companion object {
|
||||||
private const val COMPOSED_SUFFIX = "?comp"
|
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()
|
private val MEDIA_TYPE = "image/png".toMediaType()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ ext {
|
|||||||
extClass = '.LuminousScans'
|
extClass = '.LuminousScans'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://lumitoon.com'
|
baseUrl = 'https://lumitoon.com'
|
||||||
overrideVersionCode = 3
|
overrideVersionCode = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,57 +1,30 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.luminousscans
|
package eu.kanade.tachiyomi.extension.en.luminousscans
|
||||||
|
|
||||||
import android.app.Application
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt
|
||||||
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.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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.Request
|
||||||
import okhttp3.Response
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class LuminousScans :
|
class LuminousScans : MangaThemesiaAlt(
|
||||||
MangaThemesia(
|
"Luminous Scans",
|
||||||
"Luminous Scans",
|
"https://lumitoon.com",
|
||||||
"https://lumitoon.com",
|
"en",
|
||||||
"en",
|
mangaUrlDirectory = "/series",
|
||||||
mangaUrlDirectory = "/series",
|
randomUrlPrefKey = "pref_permanent_manga_url_2_en",
|
||||||
),
|
) {
|
||||||
ConfigurableSource {
|
init {
|
||||||
|
// remove legacy preferences
|
||||||
|
preferences.run {
|
||||||
|
if (contains("pref_url_map")) {
|
||||||
|
edit().remove("pref_url_map").apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val client = super.client.newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
.addInterceptor(::urlChangeInterceptor)
|
|
||||||
.rateLimit(2)
|
.rateLimit(2)
|
||||||
.build()
|
.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 {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val request = super.searchMangaRequest(page, query, filters)
|
val request = super.searchMangaRequest(page, query, filters)
|
||||||
if (query.isBlank()) return request
|
if (query.isBlank()) return request
|
||||||
@ -67,176 +40,4 @@ class LuminousScans :
|
|||||||
.url(url)
|
.url(url)
|
||||||
.build()
|
.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'
|
extClass = '.RizzComic'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://rizzcomic.com'
|
baseUrl = 'https://rizzcomic.com'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -26,53 +26,49 @@ abstract class SelectFilter(
|
|||||||
class SortFilter(defaultOrder: String? = null) : SelectFilter("Sort By", sort, defaultOrder) {
|
class SortFilter(defaultOrder: String? = null) : SelectFilter("Sort By", sort, defaultOrder) {
|
||||||
override val formParameter = "OrderValue"
|
override val formParameter = "OrderValue"
|
||||||
companion object {
|
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 POPULAR = FilterList(StatusFilter(), TypeFilter(), SortFilter("popular"))
|
||||||
val LATEST = FilterList(StatusFilter(), TypeFilter(), SortFilter("update"))
|
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) {
|
class StatusFilter : SelectFilter("Status", status) {
|
||||||
override val formParameter = "StatusValue"
|
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) {
|
class TypeFilter : SelectFilter("Type", type) {
|
||||||
override val formParameter = "TypeValue"
|
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(
|
class CheckBoxFilter(
|
||||||
name: String,
|
name: String,
|
||||||
val value: String,
|
val value: String,
|
||||||
) : Filter.CheckBox(name)
|
) : Filter.CheckBox(name)
|
||||||
|
|
||||||
class GenreFilter(
|
class GenreFilter : FormBodyFilter, Filter.Group<CheckBoxFilter>(
|
||||||
genres: List<Pair<String, String>>,
|
|
||||||
) : FormBodyFilter, Filter.Group<CheckBoxFilter>(
|
|
||||||
"Genre",
|
"Genre",
|
||||||
genres.map { CheckBoxFilter(it.first, it.second) },
|
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
|
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.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
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.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
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.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@ -22,7 +21,7 @@ import rx.Observable
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class RizzComic : MangaThemesia(
|
class RizzComic : MangaThemesiaAlt(
|
||||||
"Rizz Comic",
|
"Rizz Comic",
|
||||||
"https://rizzcomic.com",
|
"https://rizzcomic.com",
|
||||||
"en",
|
"en",
|
||||||
@ -40,43 +39,12 @@ class RizzComic : MangaThemesia(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var urlPrefix: String? = null
|
override val versionId = 2
|
||||||
private var genreCache: List<Pair<String, String>> = emptyList()
|
|
||||||
private var attempts = 0
|
|
||||||
|
|
||||||
private fun updateCache() {
|
override val slugRegex = Regex("""^r\d+-""")
|
||||||
if ((urlPrefix.isNullOrEmpty() || genreCache.isEmpty()) && attempts < 3) {
|
|
||||||
runCatching {
|
|
||||||
val document = client.newCall(GET("$baseUrl$mangaUrlDirectory", headers))
|
|
||||||
.execute().use { it.asJsoup() }
|
|
||||||
|
|
||||||
urlPrefix = document.selectFirst(".listupd a")
|
// don't allow disabling random part setting
|
||||||
?.attr("href")
|
override fun setupPreferenceScreen(screen: PreferenceScreen) { }
|
||||||
?.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")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
|
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
|
||||||
override fun popularMangaParse(response: Response) = searchMangaParse(response)
|
override fun popularMangaParse(response: Response) = searchMangaParse(response)
|
||||||
@ -103,30 +71,17 @@ class RizzComic : MangaThemesia(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
val filters: MutableList<Filter<*>> = mutableListOf(
|
return FilterList(
|
||||||
Filter.Header("Filters don't work with text search"),
|
Filter.Header("Filters don't work with text search"),
|
||||||
SortFilter(),
|
SortFilter(),
|
||||||
StatusFilter(),
|
StatusFilter(),
|
||||||
TypeFilter(),
|
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
|
@Serializable
|
||||||
class Comic(
|
class Comic(
|
||||||
val id: Int,
|
|
||||||
val title: String,
|
val title: String,
|
||||||
@SerialName("image_url") val cover: String? = null,
|
@SerialName("image_url") val cover: String? = null,
|
||||||
@SerialName("long_description") val synopsis: String? = null,
|
@SerialName("long_description") val synopsis: String? = null,
|
||||||
@ -150,13 +105,11 @@ class RizzComic : MangaThemesia(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
updateCache()
|
|
||||||
|
|
||||||
val result = response.parseAs<List<Comic>>()
|
val result = response.parseAs<List<Comic>>()
|
||||||
|
|
||||||
val entries = result.map { comic ->
|
val entries = result.map { comic ->
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = "${comic.slug}#${comic.id}"
|
url = "$mangaUrlDirectory/${comic.slug}/"
|
||||||
title = comic.title
|
title = comic.title
|
||||||
description = comic.synopsis
|
description = comic.synopsis
|
||||||
author = listOfNotNull(comic.author, comic.serialization).joinToString()
|
author = listOfNotNull(comic.author, comic.serialization).joinToString()
|
||||||
@ -166,7 +119,7 @@ class RizzComic : MangaThemesia(
|
|||||||
genre = buildList {
|
genre = buildList {
|
||||||
add(comic.type?.capitalize())
|
add(comic.type?.capitalize())
|
||||||
comic.genreIds?.onEach { gId ->
|
comic.genreIds?.onEach { gId ->
|
||||||
add(genreCache.firstOrNull { it.second == gId }?.first)
|
add(genres.firstOrNull { it.second == gId }?.first)
|
||||||
}
|
}
|
||||||
}.filterNotNull().joinToString()
|
}.filterNotNull().joinToString()
|
||||||
initialized = true
|
initialized = true
|
||||||
@ -182,37 +135,6 @@ class RizzComic : MangaThemesia(
|
|||||||
.map { mangaDetailsParse(it).apply { description = manga.description } }
|
.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 {
|
override fun imageRequest(page: Page): Request {
|
||||||
val newHeaders = headersBuilder()
|
val newHeaders = headersBuilder()
|
||||||
.set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*")
|
.set("Accept", "image/avif,image/webp,image/png,image/jpeg,*/*")
|
||||||
|
Loading…
Reference in New Issue
Block a user