AS: library badge workaround (#16858)
* AS: workaround for "in library" mark * fix
This commit is contained in:
parent
052c2f09cb
commit
89433cd894
|
@ -1,9 +1,13 @@
|
|||
package eu.kanade.tachiyomi.extension.all.asurascans
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.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
|
||||
|
@ -11,7 +15,6 @@ import kotlinx.serialization.decodeFromString
|
|||
import kotlinx.serialization.encodeToString
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
@ -20,18 +23,16 @@ 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
|
||||
|
||||
open class AsuraScans(
|
||||
override val baseUrl: String,
|
||||
override val lang: String,
|
||||
dateFormat: SimpleDateFormat,
|
||||
) : MangaThemesia(
|
||||
class AsuraScansEn : MangaThemesia(
|
||||
"Asura Scans",
|
||||
baseUrl,
|
||||
lang,
|
||||
dateFormat = dateFormat,
|
||||
"https://www.asurascans.com",
|
||||
"en",
|
||||
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
|
||||
) {
|
||||
|
||||
private val preferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
|
@ -42,35 +43,41 @@ open class AsuraScans(
|
|||
.rateLimit(1, 3, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val newManga = manga.apply {
|
||||
url = "$url#chapters#${title.toSearchQuery()}"
|
||||
override val seriesDescriptionSelector = "div.desc p, div.entry-content p, div[itemprop=description]:not(:has(p))"
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// Temp Url for manga/chapter
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val newManga = manga.permUrlToTemp()
|
||||
|
||||
return super.fetchChapterList(newManga)
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
val newManga = try {
|
||||
manga.apply {
|
||||
url = "$url#details#${title.toSearchQuery()}"
|
||||
}
|
||||
} catch (e: UninitializedPropertyAccessException) {
|
||||
// when called from deep link, title is not present
|
||||
manga
|
||||
}
|
||||
val newManga = manga.permUrlToTemp()
|
||||
|
||||
return super.fetchMangaDetails(newManga)
|
||||
}
|
||||
|
||||
// use updated url for webView
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
val dbSlug = manga.url
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
val newManga = manga.permUrlToTemp()
|
||||
|
||||
val storedSlug = getSlugMap()[dbSlug] ?: dbSlug
|
||||
|
||||
return "$baseUrl$mangaUrlDirectory/$storedSlug/"
|
||||
return baseUrl + newManga.url
|
||||
}
|
||||
|
||||
// Skip scriptPages
|
||||
|
@ -87,15 +94,65 @@ open class AsuraScans(
|
|||
else -> 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 = getSlugMap().toMutableMap()
|
||||
|
||||
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/"
|
||||
}
|
||||
putSlugMap(slugMap)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun SManga.permUrlToTemp(): SManga {
|
||||
return try {
|
||||
val dbSlug = this.url
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
|
||||
val storedSlug = getSlugMap()[dbSlug] ?: dbSlug
|
||||
|
||||
this.apply {
|
||||
url = "$mangaUrlDirectory/$storedSlug/#${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 response = chain.proceed(request)
|
||||
|
||||
val frag = request.url.fragment
|
||||
|
||||
if (frag.isNullOrEmpty()) {
|
||||
return chain.proceed(request)
|
||||
return response
|
||||
}
|
||||
|
||||
val search = frag.substringAfter("#")
|
||||
if (!response.isSuccessful && response.code == 404) {
|
||||
response.close()
|
||||
|
||||
val dbSlug = request.url.toString()
|
||||
.substringBefore("#")
|
||||
|
@ -104,21 +161,19 @@ open class AsuraScans(
|
|||
|
||||
val slugMap = getSlugMap().toMutableMap()
|
||||
|
||||
// make sure db slug key is present in the slugMap
|
||||
val storedSlug = slugMap[dbSlug] ?: dbSlug
|
||||
|
||||
val response = chain.proceed(newRequest(frag, storedSlug))
|
||||
|
||||
if (!response.isSuccessful && response.code == 404) {
|
||||
response.close()
|
||||
|
||||
val newSlug = getNewSlug(storedSlug, search)
|
||||
val newSlug = getNewSlug(storedSlug, frag)
|
||||
?: throw IOException("Migrate from Asura to Asura")
|
||||
|
||||
slugMap[dbSlug] = newSlug
|
||||
putSlugMap(slugMap)
|
||||
|
||||
return chain.proceed(newRequest(frag, newSlug))
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.url("$baseUrl$mangaUrlDirectory/$newSlug/")
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
|
@ -142,18 +197,6 @@ open class AsuraScans(
|
|||
?.substringAfterLast("/")
|
||||
}
|
||||
|
||||
private fun newRequest(frag: String, slug: String): Request {
|
||||
val manga = SManga.create().apply {
|
||||
this.url = "$mangaUrlDirectory/$slug/"
|
||||
}
|
||||
|
||||
return when (frag.substringBefore("#")) {
|
||||
"chapters" -> chapterListRequest(manga)
|
||||
"details" -> mangaDetailsRequest(manga)
|
||||
else -> throw IOException("unknown url fragment for urlChangeInterceptor")
|
||||
}
|
||||
}
|
||||
|
||||
private fun putSlugMap(slugMap: MutableMap<String, String>) {
|
||||
val serialized = json.encodeToString(slugMap)
|
||||
|
||||
|
@ -177,7 +220,24 @@ open class AsuraScans(
|
|||
.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)
|
||||
|
||||
addRandomAndCustomUserAgentPreferences(screen)
|
||||
}
|
||||
|
||||
private val SharedPreferences.permaUrlPref
|
||||
get() = getBoolean(PREF_PERM_MANGA_URL_KEY_PREFIX + lang, true)
|
||||
|
||||
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]+""")
|
|
@ -1,13 +1,6 @@
|
|||
package eu.kanade.tachiyomi.extension.all.asurascans
|
||||
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import org.jsoup.nodes.Document
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class AsuraScansFactory : SourceFactory {
|
||||
override fun createSources() = listOf(
|
||||
|
@ -15,47 +8,3 @@ class AsuraScansFactory : SourceFactory {
|
|||
AsuraScansTr(),
|
||||
)
|
||||
}
|
||||
|
||||
class AsuraScansEn : AsuraScans("https://www.asurascans.com", "en", SimpleDateFormat("MMM d, yyyy", Locale.US)) {
|
||||
|
||||
override val seriesDescriptionSelector = "div.desc p, div.entry-content p, div[itemprop=description]:not(:has(p))"
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
class AsuraScansTr : AsuraScans("https://asurascanstr.com", "tr", SimpleDateFormat("MMM d, yyyy", Locale("tr"))) {
|
||||
|
||||
override val seriesArtistSelector = ".fmed b:contains(Çizer)+span"
|
||||
override val seriesAuthorSelector = ".fmed b:contains(Yazar)+span"
|
||||
override val seriesStatusSelector = ".imptdt:contains(Durum) i"
|
||||
override val seriesTypeSelector = ".imptdt:contains(Tür) a"
|
||||
|
||||
override val altNamePrefix: String = "Alternatif isim: "
|
||||
|
||||
override fun String?.parseStatus(): Int = when {
|
||||
this == null -> SManga.UNKNOWN
|
||||
this.contains("Devam Ediyor", ignoreCase = true) -> SManga.ONGOING
|
||||
this.contains("Tamamlandı", ignoreCase = true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val scriptContent = document.selectFirst("script:containsData(ts_reader)")!!.data()
|
||||
val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");")
|
||||
val tsReader = json.decodeFromString<TSReader>(jsonString)
|
||||
val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList()
|
||||
return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TSReader(
|
||||
val sources: List<ReaderImageSource>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ReaderImageSource(
|
||||
val source: String,
|
||||
val images: List<String>,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package eu.kanade.tachiyomi.extension.all.asurascans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AsuraScansTr : MangaThemesia(
|
||||
"Asura Scans",
|
||||
"https://asurascanstr.com",
|
||||
"tr",
|
||||
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr")),
|
||||
) {
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(uaIntercept)
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.rateLimit(1, 3, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
override val seriesArtistSelector = ".fmed b:contains(Çizer)+span"
|
||||
override val seriesAuthorSelector = ".fmed b:contains(Yazar)+span"
|
||||
override val seriesStatusSelector = ".imptdt:contains(Durum) i"
|
||||
override val seriesTypeSelector = ".imptdt:contains(Tür) a"
|
||||
|
||||
override val altNamePrefix: String = "Alternatif isim: "
|
||||
|
||||
override fun String?.parseStatus(): Int = when {
|
||||
this == null -> SManga.UNKNOWN
|
||||
this.contains("Devam Ediyor", ignoreCase = true) -> SManga.ONGOING
|
||||
this.contains("Tamamlandı", ignoreCase = true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
override fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
||||
hasAttr("data-src") -> attr("abs:data-src")
|
||||
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
|
||||
else -> attr("abs:src")
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val scriptContent = document.selectFirst("script:containsData(ts_reader)")?.data()
|
||||
?: return super.pageListParse(document)
|
||||
val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");")
|
||||
val tsReader = json.decodeFromString<TSReader>(jsonString)
|
||||
val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList()
|
||||
return imageUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TSReader(
|
||||
val sources: List<ReaderImageSource>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ReaderImageSource(
|
||||
val source: String,
|
||||
val images: List<String>,
|
||||
)
|
||||
}
|
|
@ -14,7 +14,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
|
|||
override val baseVersionCode: Int = 25
|
||||
|
||||
override val sources = listOf(
|
||||
MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 21),
|
||||
MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 22),
|
||||
MultiLang("Flame Scans", "https://flamescans.org", listOf("en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 4),
|
||||
MultiLang("Komik Lab", "https://komiklab.com", listOf("en", "id"), className = "KomikLabFactory", pkgName = "komiklab", overrideVersionCode = 2),
|
||||
MultiLang("Miau Scan", "https://miauscan.com", listOf("es", "pt-BR")),
|
||||
|
|
Loading…
Reference in New Issue