251 lines
8.9 KiB
Kotlin
251 lines
8.9 KiB
Kotlin
package eu.kanade.tachiyomi.extension.all.flamescans
|
|
|
|
import android.app.Application
|
|
import android.content.SharedPreferences
|
|
import android.graphics.Bitmap
|
|
import android.graphics.BitmapFactory
|
|
import android.graphics.Canvas
|
|
import android.graphics.Rect
|
|
import androidx.preference.PreferenceScreen
|
|
import androidx.preference.SwitchPreferenceCompat
|
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
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.SChapter
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
import okhttp3.Interceptor
|
|
import okhttp3.MediaType.Companion.toMediaType
|
|
import okhttp3.OkHttpClient
|
|
import okhttp3.Protocol
|
|
import okhttp3.Response
|
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
|
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.text.SimpleDateFormat
|
|
import java.util.Locale
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
open class FlameScans(
|
|
override val baseUrl: String,
|
|
override val lang: String,
|
|
mangaUrlDirectory: String,
|
|
dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
|
|
) : MangaThemesia(
|
|
"Flame Scans",
|
|
baseUrl,
|
|
lang,
|
|
mangaUrlDirectory = mangaUrlDirectory,
|
|
dateFormat = dateFormat,
|
|
),
|
|
ConfigurableSource {
|
|
|
|
private val preferences: SharedPreferences by lazy {
|
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
}
|
|
|
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
|
.connectTimeout(10, TimeUnit.SECONDS)
|
|
.readTimeout(30, TimeUnit.SECONDS)
|
|
.addInterceptor(::composedImageIntercept)
|
|
.build()
|
|
|
|
// Split Image Fixer Start
|
|
private val composedSelector: String = "#readerarea div.figure_container div.composed_figure"
|
|
|
|
override fun pageListParse(document: Document): List<Page> {
|
|
val hasSplitImages = document
|
|
.select(composedSelector)
|
|
.firstOrNull() != null
|
|
|
|
if (!hasSplitImages) {
|
|
return super.pageListParse(document)
|
|
}
|
|
|
|
return document.select("#readerarea p:has(img), $composedSelector").toList()
|
|
.filter {
|
|
it.select("img").all { imgEl ->
|
|
imgEl.attr("abs:src").isNullOrEmpty().not()
|
|
}
|
|
}
|
|
.mapIndexed { i, el ->
|
|
if (el.tagName() == "p") {
|
|
Page(i, "", el.select("img").attr("abs:src"))
|
|
} else {
|
|
val imageUrls = el.select("img")
|
|
.joinToString("|") { it.attr("abs:src") }
|
|
|
|
Page(i, "", imageUrls + COMPOSED_SUFFIX)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun composedImageIntercept(chain: Interceptor.Chain): Response {
|
|
if (!chain.request().url.toString().endsWith(COMPOSED_SUFFIX)) {
|
|
return chain.proceed(chain.request())
|
|
}
|
|
|
|
val imageUrls = chain.request().url.toString()
|
|
.removeSuffix(COMPOSED_SUFFIX)
|
|
.split("%7C")
|
|
|
|
var width = 0
|
|
var height = 0
|
|
|
|
val imageBitmaps = imageUrls.map { imageUrl ->
|
|
val request = chain.request().newBuilder().url(imageUrl).build()
|
|
val response = chain.proceed(request)
|
|
|
|
val bitmap = BitmapFactory.decodeStream(response.body.byteStream())
|
|
|
|
width += bitmap.width
|
|
height = bitmap.height
|
|
|
|
bitmap
|
|
}
|
|
|
|
val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
val canvas = Canvas(result)
|
|
|
|
var left = 0
|
|
|
|
imageBitmaps.forEach { bitmap ->
|
|
val srcRect = Rect(0, 0, bitmap.width, bitmap.height)
|
|
val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height)
|
|
|
|
canvas.drawBitmap(bitmap, srcRect, dstRect, null)
|
|
|
|
left += bitmap.width
|
|
}
|
|
|
|
val output = ByteArrayOutputStream()
|
|
result.compress(Bitmap.CompressFormat.PNG, 100, output)
|
|
|
|
val responseBody = output.toByteArray().toResponseBody(MEDIA_TYPE)
|
|
|
|
return Response.Builder()
|
|
.code(200)
|
|
.protocol(Protocol.HTTP_1_1)
|
|
.request(chain.request())
|
|
.message("OK")
|
|
.body(responseBody)
|
|
.build()
|
|
}
|
|
// 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 {
|
|
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()
|
|
}
|
|
}
|