fuck sizurpp (#4439)

* fuck sizurpp

* update url

Co-authored-by: Rani Sargees <rani.sargees@gmail.com>
This commit is contained in:
az4521 2020-09-27 08:28:50 -04:00 committed by GitHub
parent 15c6ade5fb
commit 910716b913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 437 additions and 0 deletions

View File

@ -0,0 +1,16 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Japscan'
pkgNameSuffix = 'fr.japscan'
extClass = '.Japscan'
extVersionCode = 21
libVersion = '1.2'
}
dependencies {
implementation 'org.apache.commons:commons-lang3:3.8.1'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,421 @@
package eu.kanade.tachiyomi.extension.fr.japscan
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen
import android.view.View
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.CountDownLatch
import okhttp3.FormBody
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import org.apache.commons.lang3.StringUtils
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Japscan : ConfigurableSource, ParsedHttpSource() {
override val id: Long = 11
override val name = "Japscan"
override val baseUrl = "https://www.japscan.se"
override val lang = "fr"
override val supportsLatest = true
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@SuppressLint("SetJavaScriptEnabled")
override val client: OkHttpClient = network.cloudflareClient.newBuilder().addInterceptor { chain ->
val indicator = "&wvsc"
val cleanupjs = "var db=document.body,chl=db.children;for(db.appendChild(document.getElementsByTagName('CNV-VV')[0]);'CNV-VV'!=chl[0].tagName;)db.removeChild(chl[0]);for(var i of[].slice.call(chl[0].all_canvas)){i.style.maxWidth=(i.width+\"px\")}window.variable={w:chl[0].all_canvas[0].width,h:chl[0].all_canvas[0].height};"
val request = chain.request()
val url = request.url().toString()
val newRequest = request.newBuilder()
.url(url.substringBefore(indicator))
.build()
val response = chain.proceed(newRequest)
if (!url.endsWith(indicator)) return@addInterceptor response
// Webview screenshotting code
val handler = Handler(Looper.getMainLooper())
val latch = CountDownLatch(1)
var webView: WebView? = null
var height = 0
var width = 0
handler.post {
val webview = WebView(Injekt.get<Application>())
webView = webview
webview.settings.javaScriptEnabled = true
webview.settings.domStorageEnabled = true
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
webview.webChromeClient = object : WebChromeClient() {
@SuppressLint("NewApi")
override fun onProgressChanged(view: WebView, progress: Int) {
if (progress == 100) {
view.evaluateJavascript(cleanupjs) {
if (it.contains('{')) {
val j = JsonParser().parse(it).asJsonObject
width = j["w"].asInt
height = j["h"].asInt
latch.countDown()
} else {
webview.loadUrl(url.replace("&wvsc", ""))
}
}
}
}
}
webview.loadUrl(url.replace("&wvsc", ""))
}
latch.await()
// webView!!.isDrawingCacheEnabled = true
webView!!.measure(width + 100, height + 100)
webView!!.layout(0, 0, width + 100, height + 100)
Thread.sleep(350)
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
webView!!.draw(canvas)
// val bitmap: Bitmap = webView!!.drawingCache
val output = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
val rb = ResponseBody.create(MediaType.parse("image/png"), output.toByteArray())
response.newBuilder().body(rb).build()
}.build()
companion object {
val dateFormat by lazy {
SimpleDateFormat("dd MMM yyyy", Locale.US)
}
private const val SHOW_SPOILER_CHAPTERS_Title = "Les chapitres en Anglais ou non traduit sont upload en tant que \" Spoilers \" sur Japscan"
private const val SHOW_SPOILER_CHAPTERS = "JAPSCAN_SPOILER_CHAPTERS"
private val prefsEntries = arrayOf("Montrer uniquement les chapitres traduit en Français", "Montrer les chapitres spoiler")
private val prefsEntryValues = arrayOf("hide", "show")
}
private fun chapterListPref() = preferences.getString(SHOW_SPOILER_CHAPTERS, "hide")
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/mangas/", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
pageNumberDoc = document
val mangas = document.select(popularMangaSelector()).map { element ->
popularMangaFromElement(element)
}
val hasNextPage = false
return MangasPage(mangas, hasNextPage)
}
override fun popularMangaNextPageSelector(): String? = null
override fun popularMangaSelector() = "#top_mangas_week li > span"
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
element.select("a").first().let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text()
val s = StringUtils.stripAccents(it.text())
.replace("[\\W]".toRegex(), "-")
.replace("[-]{2,}".toRegex(), "-")
.replace("^-|-$".toRegex(), "")
manga.thumbnail_url = "$baseUrl/imgs/mangas/$s.jpg".toLowerCase(Locale.ROOT)
}
return manga
}
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET(baseUrl, headers)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector())
.distinctBy { element -> element.select("a").attr("href") }
.map { element ->
latestUpdatesFromElement(element)
}
val hasNextPage = false
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesSelector() = "#chapters > div > h3.text-truncate"
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isEmpty()) {
val uri = Uri.parse(baseUrl).buildUpon()
.appendPath("mangas")
filters.forEach { filter ->
when (filter) {
is TextField -> uri.appendPath(((page - 1) + filter.state.toInt()).toString())
is PageList -> uri.appendPath(((page - 1) + filter.values[filter.state]).toString())
}
}
return GET(uri.toString(), headers)
} else {
val formBody = FormBody.Builder()
.add("search", query)
.build()
val searchHeaders = headers.newBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
return POST("$baseUrl/live-search/", searchHeaders, formBody)
}
}
override fun searchMangaNextPageSelector(): String? = "li.page-item:last-child:not(li.active)"
override fun searchMangaSelector(): String = "div.card div.p-2, a.result-link"
override fun searchMangaParse(response: Response): MangasPage {
if ("live-search" in response.request().url().toString()) {
val body = response.body()!!.string()
val json = JsonParser().parse(body).asJsonArray
val mangas = json.map { jsonElement ->
searchMangaFromJson(jsonElement)
}
val hasNextPage = false
return MangasPage(mangas, hasNextPage)
} else {
val document = response.asJsoup()
val mangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element)
}
val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
}
}
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.select("img").attr("abs:src")
element.select("p a").let {
title = it.text()
url = it.attr("href")
}
}
private fun searchMangaFromJson(jsonElement: JsonElement): SManga = SManga.create().apply {
title = jsonElement["name"].string
url = jsonElement["url"].string
}
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#main > .card > .card-body").first()
val manga = SManga.create()
manga.thumbnail_url = "$baseUrl/${infoElement.select(".d-flex > div.m-2:eq(0) > img").attr("src")}"
infoElement.select(".d-flex > div.m-2:eq(1) > p.mb-2").forEachIndexed { _, el ->
when (el.select("span").text().trim()) {
"Auteur(s):" -> manga.author = el.text().replace("Auteur(s):", "").trim()
"Artiste(s):" -> manga.artist = el.text().replace("Artiste(s):", "").trim()
"Genre(s):" -> manga.genre = el.text().replace("Genre(s):", "").trim()
"Statut:" -> manga.status = el.text().replace("Statut:", "").trim().let {
parseStatus(it)
}
}
}
manga.description = infoElement.select("> p").text().orEmpty()
return manga
}
private fun parseStatus(status: String) = when {
status.contains("En Cours") -> SManga.ONGOING
status.contains("Terminé") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
override fun chapterListSelector() = "#chapters_list > div.collapse > div.chapters_list" +
if (chapterListPref() == "hide") { ":not(:has(.badge:contains(SPOILER),.badge:contains(RAW),.badge:contains(VUS)))" } else { "" }
// JapScan sometimes uploads some "spoiler preview" chapters, containing 2 or 3 untranslated pictures taken from a raw. Sometimes they also upload full RAWs/US versions and replace them with a translation as soon as available.
// Those have a span.badge "SPOILER" or "RAW". The additional pseudo selector makes sure to exclude these from the chapter list.
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.ownText()
// Using ownText() doesn't include childs' text, like "VUS" or "RAW" badges, in the chapter name.
chapter.date_upload = element.select("> span").text().trim().let { parseChapterDate(it) }
return chapter
}
private fun parseChapterDate(date: String): Long {
return try {
dateFormat.parse(date)?.time ?: 0
} catch (e: ParseException) {
0L
}
}
override fun pageListParse(document: Document): List<Page> {
return if (document.getElementsByTag("script").size> 12) { // scrambled images, webview screenshotting
document.getElementsByTag("option").mapIndexed { i, it -> Page(i, "", baseUrl + it.attr("value") + "&wvsc") }
} else {
// unscrambled images, check for single page
val zjsurl = document.getElementsByTag("script").first { it.attr("src").contains("zjs", ignoreCase = true) }.attr("src")
val zjs = client.newCall(GET(baseUrl + zjsurl, headers)).execute().body()!!.string()
if ((zjs.toLowerCase().split("new image").size - 1) == 1) { // single page, webview request dumping
val pagecount = document.getElementsByTag("option").size
val pages = ArrayList<Page>()
val handler = Handler(Looper.getMainLooper())
val latch = CountDownLatch(1)
val dummyimage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
val dummystream = ByteArrayOutputStream()
dummyimage.compress(Bitmap.CompressFormat.JPEG, 100, dummystream)
handler.post {
val webview = WebView(Injekt.get<Application>())
webview.settings.javaScriptEnabled = true
webview.settings.domStorageEnabled = true
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
if (request.url.toString().startsWith("https://c.")) {
pages.add(Page(pages.size, "", request.url.toString()))
if (pages.size == pagecount) { latch.countDown() }
return WebResourceResponse("image/jpeg", "UTF-8", ByteArrayInputStream(dummystream.toByteArray()))
}
return super.shouldInterceptRequest(view, request)
}
}
webview.loadUrl(baseUrl + document.getElementsByTag("option").first().attr("value"))
}
latch.await()
return pages
} else { // page by page, just do webview screenshotting because it's easier
document.getElementsByTag("option").mapIndexed { i, it -> Page(i, "", baseUrl + it.attr("value") + "&wvsc") }
}
}
}
override fun imageUrlParse(document: Document): String = ""
// Filters
private class TextField(name: String) : Filter.Text(name)
private class PageList(pages: Array<Int>) : Filter.Select<Int>("Page #", arrayOf(0, *pages))
override fun getFilterList(): FilterList {
val totalPages = pageNumberDoc?.select("li.page-item:last-child a")?.text()
val pagelist = mutableListOf<Int>()
return if (!totalPages.isNullOrEmpty()) {
for (i in 0 until totalPages.toInt()) {
pagelist.add(i + 1)
}
FilterList(
Filter.Header("Page alphabétique"),
PageList(pagelist.toTypedArray())
)
} else FilterList(
Filter.Header("Page alphabétique"),
TextField("Page #"),
Filter.Header("Appuyez sur reset pour la liste")
)
}
private var pageNumberDoc: Document? = null
// Prefs
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val chapterListPref = androidx.preference.ListPreference(screen.context).apply {
key = SHOW_SPOILER_CHAPTERS_Title
title = SHOW_SPOILER_CHAPTERS_Title
entries = prefsEntries
entryValues = prefsEntryValues
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit()
}
}
screen.addPreference(chapterListPref)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val chapterListPref = ListPreference(screen.context).apply {
key = SHOW_SPOILER_CHAPTERS_Title
title = SHOW_SPOILER_CHAPTERS_Title
entries = prefsEntries
entryValues = prefsEntryValues
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(SHOW_SPOILER_CHAPTERS, entry).commit()
}
}
screen.addPreference(chapterListPref)
}
}