Revert "AsuraScans: add auth and premium support (#11339)"
This reverts commit efa420949cea319a0389f7102f7e1ee83f8d1b50.
This commit is contained in:
parent
2a7a3f0e2b
commit
1251cbb432
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Asura Scans'
|
extName = 'Asura Scans'
|
||||||
extClass = '.AsuraScans'
|
extClass = '.AsuraScans'
|
||||||
extVersionCode = 49
|
extVersionCode = 48
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.asurascans
|
package eu.kanade.tachiyomi.extension.en.asurascans
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.webkit.CookieManager
|
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -18,27 +16,17 @@ import keiyoushi.utils.getPreferences
|
|||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import okhttp3.CookieJar
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.text.RegexOption
|
|
||||||
|
|
||||||
class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
@ -56,15 +44,6 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
private val preferences: SharedPreferences = getPreferences()
|
private val preferences: SharedPreferences = getPreferences()
|
||||||
|
|
||||||
private val application: Application by injectLazy()
|
|
||||||
private val cookieManager by lazy { CookieManager.getInstance() }
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var cachedAuthState: Boolean? = null
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var lastAuthCheck: Long = 0L
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// remove legacy preferences
|
// remove legacy preferences
|
||||||
preferences.run {
|
preferences.run {
|
||||||
@ -86,30 +65,6 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.cookieJar(
|
|
||||||
object : CookieJar {
|
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
|
||||||
if (cookies.isEmpty()) return
|
|
||||||
for (cookie in cookies) {
|
|
||||||
runCatching {
|
|
||||||
cookieManager.setCookie(url.toString(), cookie.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runCatching { cookieManager.flush() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
|
|
||||||
val cookieString = runCatching {
|
|
||||||
cookieManager.getCookie(url.toString())
|
|
||||||
}.getOrNull() ?: return mutableListOf()
|
|
||||||
|
|
||||||
return cookieString.split(';')
|
|
||||||
.mapNotNull { Cookie.parse(url, it.trim()) }
|
|
||||||
.toMutableList()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.addInterceptor(::authInterceptor)
|
|
||||||
.addInterceptor(::forceHighQualityInterceptor)
|
.addInterceptor(::forceHighQualityInterceptor)
|
||||||
.rateLimit(2, 2)
|
.rateLimit(2, 2)
|
||||||
.build()
|
.build()
|
||||||
@ -119,32 +74,19 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
private fun forceHighQualityInterceptor(chain: Interceptor.Chain): Response {
|
private fun forceHighQualityInterceptor(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
|
|
||||||
val shouldTryHighQuality = runCatching {
|
if (preferences.forceHighQuality() && !failedHighQuality && request.url.fragment == "pageListParse") {
|
||||||
request.header(HQ_ATTEMPT_HEADER) == null &&
|
OPTIMIZED_IMAGE_PATH_REGEX.find(request.url.encodedPath)?.also { match ->
|
||||||
preferences.forceHighQuality() &&
|
val (id, page) = match.destructured
|
||||||
isAuthenticated() &&
|
val newUrl = request.url.newBuilder()
|
||||||
!failedHighQuality &&
|
.encodedPath("/storage/media/$id/$page.webp")
|
||||||
request.url.fragment == "pageListParse"
|
|
||||||
}.getOrDefault(false)
|
|
||||||
|
|
||||||
if (shouldTryHighQuality) {
|
|
||||||
STANDARD_IMAGE_PATH_REGEX.find(request.url.encodedPath)?.also { match ->
|
|
||||||
val (id, filename) = match.destructured
|
|
||||||
val optimizedName = "$filename-optimized.webp"
|
|
||||||
val optimizedUrl = request.url.newBuilder()
|
|
||||||
.encodedPath("/storage/media/$id/conversions/$optimizedName")
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val hiResRequest = request.newBuilder()
|
val response = chain.proceed(request.newBuilder().url(newUrl).build())
|
||||||
.url(optimizedUrl)
|
if (response.code != 404) {
|
||||||
.header(HQ_ATTEMPT_HEADER, "1")
|
|
||||||
.build()
|
|
||||||
val response = runCatching { chain.proceed(hiResRequest) }.getOrNull()
|
|
||||||
if (response?.isSuccessful == true) {
|
|
||||||
return response
|
return response
|
||||||
} else {
|
} else {
|
||||||
failedHighQuality = true
|
failedHighQuality = true
|
||||||
response?.close()
|
response.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,21 +94,6 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authInterceptor(chain: Interceptor.Chain): Response {
|
|
||||||
val response = chain.proceed(chain.request())
|
|
||||||
|
|
||||||
if (response.code == 401 || response.code == 403) {
|
|
||||||
handleSessionExpiry(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
val location = response.header("Location")
|
|
||||||
if (location != null && location.contains("/login", ignoreCase = true)) {
|
|
||||||
handleSessionExpiry(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
@ -339,14 +266,8 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||||
|
|
||||||
override fun chapterListSelector(): String {
|
override fun chapterListSelector() =
|
||||||
val authenticated = runCatching { isAuthenticated() }.getOrDefault(false)
|
if (preferences.hidePremiumChapters()) "div.scrollbar-thumb-themecolor > div.group:not(:has(svg))" else "div.scrollbar-thumb-themecolor > div.group"
|
||||||
return when {
|
|
||||||
authenticated -> "div.scrollbar-thumb-themecolor > div.group"
|
|
||||||
preferences.hidePremiumChapters() -> "div.scrollbar-thumb-themecolor > div.group:not(:has(svg))"
|
|
||||||
else -> "div.scrollbar-thumb-themecolor > div.group"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href").toPermSlugIfNeeded())
|
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href").toPermSlugIfNeeded())
|
||||||
@ -372,157 +293,24 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val chapterMeta = document.extractChapterMetadata()
|
val scriptData = document.select("script:containsData(self.__next_f.push)")
|
||||||
val isPremium = chapterMeta?.isEarlyAccess == true
|
.joinToString("") { it.data().substringAfter("\"").substringBeforeLast("\"") }
|
||||||
|
val pagesData = PAGES_REGEX.find(scriptData)?.groupValues?.get(1) ?: throw Exception("Failed to find chapter pages")
|
||||||
if (isPremium) {
|
|
||||||
val chapterId = chapterMeta?.id ?: throw Exception("Chapter metadata not found")
|
|
||||||
if (!isAuthenticated()) {
|
|
||||||
throw Exception(PREMIUM_AUTH_MESSAGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
val unlockData = unlockChapter(chapterId)
|
|
||||||
val unlockToken = unlockData.unlockToken ?: run {
|
|
||||||
resetAuthCache()
|
|
||||||
throw Exception("Missing unlock token. Please login again.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val quality = MEDIA_QUALITY_MAX
|
|
||||||
val orderedPages = unlockData.pages.sortedBy { it.order }
|
|
||||||
if (orderedPages.isEmpty()) {
|
|
||||||
throw Exception("Premium chapter pages unavailable.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderedPages.mapIndexed { index, page ->
|
|
||||||
val imageUrl = fetchMediaUrl(page.id, chapterId, unlockToken, quality)
|
|
||||||
Page(index, imageUrl = appendPageFragment(imageUrl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseStandardPageList(document)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseStandardPageList(document: Document): List<Page> {
|
|
||||||
val scriptElement = document.select("script")
|
|
||||||
.firstOrNull { PAGES_REGEX.containsMatchIn(it.data()) }
|
|
||||||
?: throw Exception("Failed to find chapter pages")
|
|
||||||
val scriptData = scriptElement.data()
|
|
||||||
val pagesData = PAGES_REGEX.find(scriptData)?.groupValues?.get(1)
|
|
||||||
?: throw Exception("Failed to find chapter pages")
|
|
||||||
val pageList = json.decodeFromString<List<PageDto>>(pagesData.unescape()).sortedBy { it.order }
|
val pageList = json.decodeFromString<List<PageDto>>(pagesData.unescape()).sortedBy { it.order }
|
||||||
return pageList.mapIndexed { index, page ->
|
return pageList.mapIndexed { i, page ->
|
||||||
Page(index, imageUrl = appendPageFragment(page.url))
|
val newUrl = page.url.toHttpUrlOrNull()?.run {
|
||||||
}
|
newBuilder()
|
||||||
}
|
.fragment("pageListParse")
|
||||||
|
.build()
|
||||||
private fun Document.extractChapterMetadata(): ChapterMetadata? {
|
.toString()
|
||||||
val script = select("script")
|
|
||||||
.map { it.data() }
|
|
||||||
.firstOrNull { it.contains(CHAPTER_DATA_TOKEN) }
|
|
||||||
?: return null
|
|
||||||
val match = CHAPTER_DATA_REGEX.find(script) ?: return null
|
|
||||||
val id = match.groupValues[1].toIntOrNull() ?: return null
|
|
||||||
val isEarly = match.groupValues[2].toBoolean()
|
|
||||||
return ChapterMetadata(id, isEarly)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun appendPageFragment(url: String): String {
|
|
||||||
return url.toHttpUrlOrNull()?.newBuilder()
|
|
||||||
?.fragment("pageListParse")
|
|
||||||
?.build()
|
|
||||||
?.toString()
|
|
||||||
?: url
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildApiPostRequest(url: String, body: RequestBody): Request {
|
|
||||||
val builder = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.headers(headersBuilder().build())
|
|
||||||
.post(body)
|
|
||||||
.addHeader("Content-Type", "application/json")
|
|
||||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
|
||||||
|
|
||||||
getXsrfToken()?.let { token ->
|
|
||||||
builder.addHeader("X-XSRF-TOKEN", token)
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getXsrfToken(): String? {
|
|
||||||
val cookieToken = sequence {
|
|
||||||
apiUrl.toHttpUrlOrNull()?.let { yield(it) }
|
|
||||||
baseUrl.toHttpUrlOrNull()?.let { yield(it) }
|
|
||||||
}.mapNotNull { httpUrl ->
|
|
||||||
client.cookieJar.loadForRequest(httpUrl)
|
|
||||||
.firstOrNull { it.name.equals("XSRF-TOKEN", ignoreCase = true) }
|
|
||||||
?.value
|
|
||||||
}.firstOrNull()
|
|
||||||
|
|
||||||
return decodeCookieValue(cookieToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeCookieValue(value: String?): String? {
|
|
||||||
if (value.isNullOrEmpty()) return null
|
|
||||||
return runCatching { URLDecoder.decode(value, StandardCharsets.UTF_8.name()) }.getOrDefault(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unlockChapter(chapterId: Int): UnlockDataDto {
|
|
||||||
val payload = json.encodeToString(UnlockRequestDto(chapterId))
|
|
||||||
val body = payload.toRequestBody(JSON_MEDIA_TYPE)
|
|
||||||
val request = buildApiPostRequest("$apiUrl/chapter/unlock", body)
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
val responseBody = response.body?.string() ?: throw Exception("Empty unlock response")
|
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
response.close()
|
|
||||||
if (response.code == 401 || response.code == 403 || response.code == 419) {
|
|
||||||
resetAuthCache()
|
|
||||||
throw Exception("Session expired. Please login again.")
|
|
||||||
}
|
|
||||||
throw Exception("Unable to unlock premium chapter. (${response.code})")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val unlock = json.decodeFromString<UnlockResponseDto>(responseBody)
|
Page(i, imageUrl = newUrl ?: page.url)
|
||||||
val data = unlock.data
|
|
||||||
if (!unlock.success || data == null || data.unlockToken.isNullOrEmpty()) {
|
|
||||||
resetAuthCache()
|
|
||||||
throw Exception(unlock.message ?: "Unable to unlock premium chapter. Please login again.")
|
|
||||||
}
|
|
||||||
cachedAuthState = true
|
|
||||||
lastAuthCheck = System.currentTimeMillis()
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchMediaUrl(mediaId: Int, chapterId: Int, token: String, quality: String): String {
|
|
||||||
val payload = json.encodeToString(MediaRequestDto(mediaId, chapterId, token, quality))
|
|
||||||
val body = payload.toRequestBody(JSON_MEDIA_TYPE)
|
|
||||||
val request = buildApiPostRequest("$apiUrl/media", body)
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
val responseBody = response.body?.string() ?: throw Exception("Empty media response")
|
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
response.close()
|
|
||||||
if (response.code == 401 || response.code == 403 || response.code == 419) {
|
|
||||||
resetAuthCache()
|
|
||||||
throw Exception("Session expired while fetching media. Please login again.")
|
|
||||||
}
|
|
||||||
throw Exception("Unable to fetch media URL. (${response.code})")
|
|
||||||
}
|
|
||||||
|
|
||||||
val media = json.decodeFromString<MediaResponseDto>(responseBody)
|
|
||||||
return appendPageFragment(media.data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
private data class ChapterMetadata(
|
|
||||||
val id: Int,
|
|
||||||
val isEarlyAccess: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
private enum class FiltersState { NOT_FETCHED, FETCHING, FETCHED }
|
private enum class FiltersState { NOT_FETCHED, FETCHING, FETCHED }
|
||||||
|
|
||||||
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
|
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
|
||||||
@ -545,19 +333,12 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
key = PREF_FORCE_HIGH_QUALITY
|
key = PREF_FORCE_HIGH_QUALITY
|
||||||
title = "Enable max quality"
|
title = "Force high quality chapter images"
|
||||||
val baseSummary = "Asura+ Basic/Premium subscribers can request optimized max quality images. Requires authentication. Increases bandwidth by ~50%."
|
summary = "Attempt to use high quality chapter images.\nWill increase bandwidth by ~50%."
|
||||||
summary = if (failedHighQuality) {
|
if (failedHighQuality) {
|
||||||
"$baseSummary\n*DISABLED* because of missing max quality images."
|
summary = "$summary\n*DISABLED* because of missing high quality images."
|
||||||
} else {
|
|
||||||
baseSummary
|
|
||||||
}
|
}
|
||||||
setDefaultValue(false)
|
setDefaultValue(false)
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
|
||||||
failedHighQuality = false
|
|
||||||
summary = baseSummary
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}.let(screen::addPreference)
|
}.let(screen::addPreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,66 +379,13 @@ class AsuraScans : ParsedHttpSource(), ConfigurableSource {
|
|||||||
return UNESCAPE_REGEX.replace(this, "$1")
|
return UNESCAPE_REGEX.replace(this, "$1")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSessionExpiry(response: Response): Nothing {
|
|
||||||
resetAuthCache()
|
|
||||||
response.close()
|
|
||||||
throw Exception("Authentication failed. Please login again via WebView.")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetAuthCache() {
|
|
||||||
cachedAuthState = null
|
|
||||||
lastAuthCheck = 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAuthenticated(force: Boolean = false): Boolean {
|
|
||||||
// Check if we have WebView cookies
|
|
||||||
val hasWebViewCookies = runCatching {
|
|
||||||
cookieManager.getCookie("https://$ASURA_MAIN_HOST")?.isNotEmpty() == true ||
|
|
||||||
cookieManager.getCookie("https://$ASURA_API_HOST")?.isNotEmpty() == true
|
|
||||||
}.getOrDefault(false)
|
|
||||||
|
|
||||||
if (!hasWebViewCookies) {
|
|
||||||
cachedAuthState = false
|
|
||||||
lastAuthCheck = System.currentTimeMillis()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
val cached = cachedAuthState
|
|
||||||
if (!force && cached != null && now - lastAuthCheck < AUTH_CACHE_DURATION) {
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = GET("$apiUrl/user", headersBuilder().build())
|
|
||||||
val isAuthed = runCatching { client.newCall(request).execute() }.getOrNull()?.use { resp ->
|
|
||||||
if (!resp.isSuccessful) return@use false
|
|
||||||
val body = resp.body?.string() ?: return@use false
|
|
||||||
val root = runCatching { json.parseToJsonElement(body).jsonObject }.getOrNull() ?: return@use false
|
|
||||||
root["data"] != null
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
cachedAuthState = isAuthed
|
|
||||||
lastAuthCheck = now
|
|
||||||
return isAuthed
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val UNESCAPE_REGEX = """\\(.)""".toRegex()
|
private val UNESCAPE_REGEX = """\\(.)""".toRegex()
|
||||||
private val PAGES_REGEX = """\\"pages\\":(\[.*?])""".toRegex()
|
private val PAGES_REGEX = """\\"pages\\":(\[.*?])""".toRegex()
|
||||||
private val CLEAN_DATE_REGEX = """(\d+)(st|nd|rd|th)""".toRegex()
|
private val CLEAN_DATE_REGEX = """(\d+)(st|nd|rd|th)""".toRegex()
|
||||||
private val OLD_FORMAT_MANGA_REGEX = """^/manga/(\d+-)?([^/]+)/?$""".toRegex()
|
private val OLD_FORMAT_MANGA_REGEX = """^/manga/(\d+-)?([^/]+)/?$""".toRegex()
|
||||||
private val OLD_FORMAT_CHAPTER_REGEX = """^/(\d+-)?[^/]*-chapter-\d+(-\d+)*/?$""".toRegex()
|
private val OLD_FORMAT_CHAPTER_REGEX = """^/(\d+-)?[^/]*-chapter-\d+(-\d+)*/?$""".toRegex()
|
||||||
private val STANDARD_IMAGE_PATH_REGEX = """^/storage/media/(\d+)/([^/]+?)\.[^./]+$""".toRegex(RegexOption.IGNORE_CASE)
|
private val OPTIMIZED_IMAGE_PATH_REGEX = """^/storage/media/(\d+)/conversions/(.*)-optimized\.webp$""".toRegex()
|
||||||
private val CHAPTER_DATA_REGEX = """\\"chapter\\":\{\\"id\\":(\d+).*?\\"is_early_access\\":(true|false)""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
|
||||||
|
|
||||||
private const val ASURA_MAIN_HOST = "asuracomic.net"
|
|
||||||
private const val ASURA_API_HOST = "gg.asuracomic.net"
|
|
||||||
private const val AUTH_CACHE_DURATION = 60_000L
|
|
||||||
private const val CHAPTER_DATA_TOKEN = """\"chapter\":"""
|
|
||||||
private const val PREMIUM_AUTH_MESSAGE = "Premium chapter requires authentication. Login via WebView."
|
|
||||||
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaType()
|
|
||||||
private const val MEDIA_QUALITY_MAX = "max-quality"
|
|
||||||
private const val HQ_ATTEMPT_HEADER = "X-Asura-HQ-Attempt"
|
|
||||||
|
|
||||||
private const val PREF_SLUG_MAP = "pref_slug_map_2"
|
private const val PREF_SLUG_MAP = "pref_slug_map_2"
|
||||||
private const val PREF_DYNAMIC_URL = "pref_dynamic_url"
|
private const val PREF_DYNAMIC_URL = "pref_dynamic_url"
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.asurascans
|
package eu.kanade.tachiyomi.extension.en.asurascans
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -21,51 +20,3 @@ class PageDto(
|
|||||||
val order: Int,
|
val order: Int,
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class UnlockRequestDto(
|
|
||||||
@SerialName("chapterId")
|
|
||||||
val chapterId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class UnlockResponseDto(
|
|
||||||
val success: Boolean,
|
|
||||||
val data: UnlockDataDto? = null,
|
|
||||||
val message: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class UnlockDataDto(
|
|
||||||
val id: Int,
|
|
||||||
val name: Int,
|
|
||||||
val title: String? = null,
|
|
||||||
@SerialName("is_early_access")
|
|
||||||
val isEarlyAccess: Boolean,
|
|
||||||
@SerialName("unlock_token")
|
|
||||||
val unlockToken: String? = null,
|
|
||||||
val pages: List<UnlockPageDto> = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class UnlockPageDto(
|
|
||||||
val order: Int,
|
|
||||||
val id: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class MediaRequestDto(
|
|
||||||
@SerialName("media_id")
|
|
||||||
val mediaId: Int,
|
|
||||||
@SerialName("chapter_id")
|
|
||||||
val chapterId: Int,
|
|
||||||
val token: String,
|
|
||||||
val quality: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class MediaResponseDto(
|
|
||||||
val data: String,
|
|
||||||
@SerialName("content-type")
|
|
||||||
val contentType: String? = null,
|
|
||||||
)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user