parent
509cc58346
commit
73013e9d46
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Sussy Toons'
|
extName = 'Sussy Toons'
|
||||||
extClass = '.SussyToons'
|
extClass = '.SussyToons'
|
||||||
extVersionCode = 44
|
extVersionCode = 45
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.sussyscan
|
package eu.kanade.tachiyomi.extension.pt.sussyscan
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Base64
|
import android.app.Application
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
@ -19,8 +25,12 @@ import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SussyToons : HttpSource() {
|
class SussyToons : HttpSource() {
|
||||||
|
@ -44,6 +54,7 @@ class SussyToons : HttpSource() {
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.rateLimit(1, 2, TimeUnit.SECONDS)
|
.rateLimit(1, 2, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(::chapterPages)
|
||||||
.addInterceptor(::imageLocation)
|
.addInterceptor(::imageLocation)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -154,25 +165,10 @@ class SussyToons : HttpSource() {
|
||||||
// ============================= Pages ====================================
|
// ============================= Pages ====================================
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val pageHeaders = buildPageListHeaders(chapter)
|
val url = "$apiUrl${chapter.url}".toHttpUrl().newBuilder()
|
||||||
return GET("$apiUrl${chapter.url}", pageHeaders)
|
.fragment(getChapterUrl(chapter))
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildPageListHeaders(chapter: SChapter): Headers {
|
|
||||||
val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
|
||||||
val xClientHash = when {
|
|
||||||
chapter.id.toInt() % 2 != 0 -> ""
|
|
||||||
else -> (headers.get("User-Agent") ?: "").md5()
|
|
||||||
}
|
|
||||||
val pageHeaders = headers.newBuilder()
|
|
||||||
.set("Accept", "application/json, text/plain, */*")
|
|
||||||
.set("Accept-Language", "pt-br,pt;q=0.9,en-us;q=0.8,en;q=0.7")
|
|
||||||
.set("Origin", baseUrl)
|
|
||||||
.set("Referer", "$baseUrl/")
|
|
||||||
.set("x-client-hash", xClientHash)
|
|
||||||
.set("x-timestamp", timestamp.toString())
|
|
||||||
.build()
|
.build()
|
||||||
return pageHeaders
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
@ -195,25 +191,9 @@ class SussyToons : HttpSource() {
|
||||||
return GET(page.url, imageHeaders)
|
return GET(page.url, imageHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================= Utilities ====================================
|
// ============================= Interceptors =================================
|
||||||
|
|
||||||
private fun MangaDto.toSManga(): SManga {
|
private var chapterPageHeaders: Headers? = null
|
||||||
val sManga = SManga.create().apply {
|
|
||||||
title = name
|
|
||||||
thumbnail_url = thumbnail
|
|
||||||
initialized = true
|
|
||||||
val mangaUrl = "$baseUrl/obra".toHttpUrl().newBuilder()
|
|
||||||
.addPathSegment(this@toSManga.id.toString())
|
|
||||||
.addPathSegment(this@toSManga.slug!!)
|
|
||||||
.build()
|
|
||||||
setUrlWithoutDomain(mangaUrl.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
Jsoup.parseBodyFragment(description).let { sManga.description = it.text() }
|
|
||||||
sManga.status = status.toStatus()
|
|
||||||
|
|
||||||
return sManga
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun imageLocation(chain: Interceptor.Chain): Response {
|
private fun imageLocation(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
|
@ -235,6 +215,115 @@ class SussyToons : HttpSource() {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun chapterPages(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val chapterUrl = request.url.fragment?.takeIf { it.contains("capitulo") }
|
||||||
|
?: return chain.proceed(request)
|
||||||
|
|
||||||
|
val originUrl = request.url.newBuilder()
|
||||||
|
.fragment(null)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val newRequest = request.newBuilder()
|
||||||
|
.url(originUrl)
|
||||||
|
|
||||||
|
chapterPageHeaders?.let { headers ->
|
||||||
|
newRequest.headers(headers)
|
||||||
|
val response = chain.proceed(newRequest.build())
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
response.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
val chapterPageRequest = request.newBuilder()
|
||||||
|
.url(chapterUrl)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return chain.proceed(fetchChapterPagesHeaders(chapterPageRequest, newRequest.build()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
private fun fetchChapterPagesHeaders(baseRequest: Request, originRequest: Request): Request {
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
val headers = originRequest.headers.newBuilder()
|
||||||
|
var webView: WebView? = null
|
||||||
|
val looper = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
looper.post {
|
||||||
|
webView = WebView(Injekt.get<Application>())
|
||||||
|
webView?.let {
|
||||||
|
with(it.settings) {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
blockNetworkImage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView?.webViewClient = object : WebViewClient() {
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest,
|
||||||
|
): WebResourceResponse? {
|
||||||
|
val ignore = listOf(".css", "google", "fonts", "ads")
|
||||||
|
val url = request.url.toString()
|
||||||
|
if (ignore.any { url.contains(it, ignoreCase = true) }) {
|
||||||
|
return emptyResource()
|
||||||
|
}
|
||||||
|
if (request.isOriginRequest() && request.method.equals("GET", true)) {
|
||||||
|
headers.fill(request.requestHeaders)
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
return super.shouldInterceptRequest(view, request)
|
||||||
|
}
|
||||||
|
private fun WebResourceRequest.isOriginRequest() =
|
||||||
|
originRequest.url.toString().equals(this.url.toString(), ignoreCase = true)
|
||||||
|
|
||||||
|
private fun emptyResource() = WebResourceResponse(null, null, null)
|
||||||
|
}
|
||||||
|
webView?.loadUrl(baseRequest.url.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await(client.readTimeoutMillis.toLong(), TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
looper.post {
|
||||||
|
webView?.run {
|
||||||
|
stopLoading()
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chapterPageHeaders = headers.build()
|
||||||
|
|
||||||
|
return originRequest.newBuilder()
|
||||||
|
.headers(chapterPageHeaders!!)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Headers.Builder.fill(from: Map<String, String>): Headers.Builder {
|
||||||
|
return from.entries.fold(this) { builder, entry ->
|
||||||
|
builder.set(entry.key, entry.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================= Utilities ====================================
|
||||||
|
|
||||||
|
private fun MangaDto.toSManga(): SManga {
|
||||||
|
val sManga = SManga.create().apply {
|
||||||
|
title = name
|
||||||
|
thumbnail_url = thumbnail
|
||||||
|
initialized = true
|
||||||
|
val mangaUrl = "$baseUrl/obra".toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment(this@toSManga.id.toString())
|
||||||
|
.addPathSegment(this@toSManga.slug!!)
|
||||||
|
.build()
|
||||||
|
setUrlWithoutDomain(mangaUrl.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
Jsoup.parseBodyFragment(description).let { sManga.description = it.text() }
|
||||||
|
sManga.status = status.toStatus()
|
||||||
|
|
||||||
|
return sManga
|
||||||
|
}
|
||||||
|
|
||||||
private inline fun <reified T> Response.parseAs(): T {
|
private inline fun <reified T> Response.parseAs(): T {
|
||||||
return json.decodeFromStream(body.byteStream())
|
return json.decodeFromStream(body.byteStream())
|
||||||
}
|
}
|
||||||
|
@ -242,16 +331,11 @@ class SussyToons : HttpSource() {
|
||||||
private fun String.toDate() =
|
private fun String.toDate() =
|
||||||
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L }
|
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L }
|
||||||
|
|
||||||
private fun String.md5(): String {
|
|
||||||
// Base64.NO_WRAP avoids HTTP error in header encoding on special characters (\n,\r, etc)
|
|
||||||
return Base64.encodeToString(this.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CDN_URL = "https://usc1.contabostorage.com/23b45111d96c42c18a678c1d8cba7123:cdn"
|
const val CDN_URL = "https://usc1.contabostorage.com/23b45111d96c42c18a678c1d8cba7123:cdn"
|
||||||
const val OLDI_URL = "https://oldi.sussytoons.site"
|
const val OLDI_URL = "https://oldi.sussytoons.site"
|
||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue