AS: library badge workaround (#16858)

* AS: workaround for "in library" mark

* fix
This commit is contained in:
AwkwardPeak7 2023-06-25 23:33:45 +05:00 committed by GitHub
parent 052c2f09cb
commit 89433cd894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 108 deletions

View File

@ -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 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.apply {
url = "$url#chapters#${title.toSearchQuery()}"
}
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,38 +94,86 @@ open class AsuraScans(
else -> attr("abs:src")
}
private fun urlChangeInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val frag = request.url.fragment
if (frag.isNullOrEmpty()) {
return chain.proceed(request)
private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
return this.map { mangasPage ->
MangasPage(
mangasPage.mangas.map { it.tempUrlToPermIfNeeded() },
mangasPage.hasNextPage,
)
}
}
val search = frag.substringAfter("#")
val dbSlug = request.url.toString()
.substringBefore("#")
.removeSuffix("/")
.substringAfterLast("/")
private fun SManga.tempUrlToPermIfNeeded(): SManga {
if (!preferences.permaUrlPref) return this
val slugMap = getSlugMap().toMutableMap()
// make sure db slug key is present in the slugMap
val storedSlug = slugMap[dbSlug] ?: dbSlug
val sMangaTitleFirstWord = this.title.split(" ")[0]
if (!this.url.contains("/$sMangaTitleFirstWord", ignoreCase = true)) {
val currentSlug = this.url
.removeSuffix("/")
.substringAfterLast("/")
val response = chain.proceed(newRequest(frag, storedSlug))
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 response
}
if (!response.isSuccessful && response.code == 404) {
response.close()
val newSlug = getNewSlug(storedSlug, search)
val dbSlug = request.url.toString()
.substringBefore("#")
.removeSuffix("/")
.substringAfterLast("/")
val slugMap = getSlugMap().toMutableMap()
val storedSlug = slugMap[dbSlug] ?: dbSlug
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]+""")

View File

@ -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>,
)
}

View File

@ -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>,
)
}

View File

@ -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")),