Fix (Raijin Scans) : Update for site changes (#9172)
* Refactor RaijinScans extension: update to HttpSource and add LatestUpdatesDto class for new site * Fix VersionCode * Fix review * Fix version Code
This commit is contained in:
parent
fcc13a63ed
commit
0c3f9f2736
@ -1,9 +1,8 @@
|
||||
ext {
|
||||
extName = 'Raijin Scans'
|
||||
extClass = '.RaijinScans'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://raijinscan.fr'
|
||||
overrideVersionCode = 4
|
||||
extVersionCode = 47
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,202 @@
|
||||
package eu.kanade.tachiyomi.extension.fr.raijinscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import java.text.SimpleDateFormat
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
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.HttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class RaijinScans : Madara("Raijin Scans", "https://raijinscan.fr", "fr", dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH)) {
|
||||
override val useNewChapterEndpoint = true
|
||||
class RaijinScans : HttpSource() {
|
||||
|
||||
override val name = "Raijin Scans"
|
||||
override val baseUrl = "https://raijinscan.fr"
|
||||
override val lang = "fr"
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient
|
||||
|
||||
private var nonce: String? = null
|
||||
|
||||
private val nonceRegex = """"nonce"\s*:\s*"([^"]+)"""".toRegex()
|
||||
private val numberRegex = """(\d+)""".toRegex()
|
||||
private val descriptionScriptRegex = """content\.innerHTML = `([\s\S]+?)`;""".toRegex()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
val mangas = document.select("section#most-viewed div.swiper-slide.unit").map(::popularMangaFromElement)
|
||||
return MangasPage(mangas, false)
|
||||
}
|
||||
|
||||
private fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
val titleElement = element.selectFirst("a.c-title")!!
|
||||
setUrlWithoutDomain(titleElement.attr("abs:href"))
|
||||
title = titleElement.text()
|
||||
thumbnail_url = element.selectFirst("a.poster div.poster-image-wrapper > img")?.attr("abs:src")
|
||||
}
|
||||
|
||||
// ================================ Recent ================================
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
if (page == 1) {
|
||||
return GET(baseUrl, headers)
|
||||
}
|
||||
|
||||
val currentNonce = nonce
|
||||
?: throw Exception("Nonce not found. Please try refreshing by pulling down on the 'Recent' page.")
|
||||
|
||||
val formBody = FormBody.Builder()
|
||||
.add("action", "load_manga")
|
||||
.add("page", (page - 1).toString())
|
||||
.add("nonce", currentNonce)
|
||||
.build()
|
||||
|
||||
val xhrHeaders = headersBuilder()
|
||||
.set("X-Requested-With", "XMLHttpRequest")
|
||||
.set("Accept", "*/*")
|
||||
.add("Origin", baseUrl)
|
||||
.build()
|
||||
|
||||
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
if (response.request.method == "GET") {
|
||||
val document = response.asJsoup()
|
||||
val scriptElement = document.selectFirst("script#ajax-sh-js-extra")?.data()
|
||||
nonce = scriptElement?.let { nonceRegex.find(it)?.groupValues?.get(1) }
|
||||
|
||||
val mangas = document.select("section.recently-updated div.unit").map(::searchMangaFromElement)
|
||||
val hasNextPage = document.selectFirst("a#load-more-manga") != null
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
val data = response.parseAs<LatestUpdatesDto>()
|
||||
|
||||
if (!data.success) {
|
||||
return MangasPage(emptyList(), false)
|
||||
}
|
||||
|
||||
val documentFragment = Jsoup.parseBodyFragment(data.data.mangaHtml, baseUrl)
|
||||
val mangas = documentFragment.select("div.unit").map(::searchMangaFromElement)
|
||||
val hasNextPage = data.data.currentPage < data.data.totalPages
|
||||
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
// =============================== Search ==============================
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
if (page > 1) addPathSegment("page").addPathSegment(page.toString())
|
||||
addQueryParameter("s", query)
|
||||
addQueryParameter("post_type", "wp-manga")
|
||||
}.build()
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
val mangas = document.select("div.original.card-lg div.unit").map(::searchMangaFromElement)
|
||||
val hasNextPage = document.selectFirst("li.page-item:not(.disabled) a[rel=next]") != null
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
private fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
val linkElement = element.selectFirst("div.info > a")!!
|
||||
setUrlWithoutDomain(linkElement.attr("abs:href"))
|
||||
title = linkElement.text()
|
||||
thumbnail_url = element.selectFirst("div.poster-image-wrapper > img")?.attr("abs:src")
|
||||
}
|
||||
|
||||
// =========================== Manga Details ===========================
|
||||
private fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
title = document.selectFirst("h1.serie-title")!!.text()
|
||||
author = document.selectFirst("div.stat-item:has(span:contains(Auteur)) span.stat-value")?.text()
|
||||
artist = document.selectFirst("div.stat-item:has(span:contains(Artiste)) span.stat-value")?.text()
|
||||
|
||||
val scriptDescription = document.select("script:containsData(content.innerHTML)")
|
||||
.firstNotNullOfOrNull { descriptionScriptRegex.find(it.data())?.groupValues?.get(1)?.trim() }
|
||||
description = scriptDescription ?: document.selectFirst("div.description-content")?.text()
|
||||
|
||||
genre = document.select("div.genre-list div.genre-link").joinToString { it.text() }
|
||||
|
||||
thumbnail_url = document.selectFirst("img.cover")?.attr("abs:src")
|
||||
status = parseStatus(document.selectFirst("div.stat-item:has(span:contains(État du titre)) span.manga")?.text())
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga = mangaDetailsParse(response.asJsoup())
|
||||
|
||||
private fun parseStatus(status: String?): Int {
|
||||
return when {
|
||||
status == null -> SManga.UNKNOWN
|
||||
status.contains("En cours", ignoreCase = true) -> SManga.ONGOING
|
||||
status.contains("Terminé", ignoreCase = true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Chapter List ==========================
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return response.asJsoup().select("ul.scroll-sm li.item").map(::chapterFromElement)
|
||||
}
|
||||
|
||||
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
val link = element.selectFirst("a")!!
|
||||
setUrlWithoutDomain(link.attr("abs:href"))
|
||||
name = link.attr("title").trim()
|
||||
|
||||
date_upload = parseRelativeDateString(link.selectFirst("> span:nth-of-type(2)")?.text())
|
||||
}
|
||||
|
||||
private fun parseRelativeDateString(date: String?): Long {
|
||||
if (date == null) return 0L
|
||||
|
||||
val lcDate = date.lowercase(Locale.FRENCH).trim()
|
||||
val cal = Calendar.getInstance()
|
||||
val number = numberRegex.find(lcDate)?.value?.toIntOrNull()
|
||||
|
||||
return when {
|
||||
"aujourd'hui" in lcDate -> cal.timeInMillis
|
||||
"hier" in lcDate -> cal.apply { add(Calendar.DAY_OF_MONTH, -1) }.timeInMillis
|
||||
number != null -> when {
|
||||
("h" in lcDate || "heure" in lcDate) && "chapitre" !in lcDate -> cal.apply { add(Calendar.HOUR_OF_DAY, -number) }.timeInMillis
|
||||
"min" in lcDate -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
|
||||
"jour" in lcDate || lcDate.endsWith("j") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
|
||||
"semaine" in lcDate -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
|
||||
"mois" in lcDate || (lcDate.endsWith("m") && "min" !in lcDate) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
|
||||
"an" in lcDate -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
|
||||
else -> 0L
|
||||
}
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Page List =============================
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
return response.asJsoup().select("div.protected-image-data").mapIndexed { index, element ->
|
||||
val encodedUrl = element.attr("data-src")
|
||||
val imageUrl = String(Base64.decode(encodedUrl, Base64.DEFAULT))
|
||||
Page(index, imageUrl = imageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used.")
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package eu.kanade.tachiyomi.extension.fr.raijinscans
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class LatestUpdatesDto(
|
||||
val success: Boolean,
|
||||
val data: LatestUpdatesDataDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class LatestUpdatesDataDto(
|
||||
@SerialName("manga_html") val mangaHtml: String,
|
||||
@SerialName("current_page") val currentPage: Int,
|
||||
@SerialName("total_pages") val totalPages: Int,
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user