AnimeSama: Fix Website changes (#3939)

* AnimeSama: Full Rewrite

* AnimeSama: Fix "Special" chapters

* AnimeSama: Changes asked by AwkwardPeak7

* AnimeSama: Maybe better containsMultipleTimes function

* AnimeSama: Changes from AwkwardPeak7
This commit is contained in:
CriosChan 2024-07-13 08:07:56 +02:00 committed by Draff
parent cb1d65d02c
commit cb4c648b19
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
2 changed files with 107 additions and 69 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'AnimeSama' extName = 'AnimeSama'
extClass = '.AnimeSama' extClass = '.AnimeSama'
extVersionCode = 1 extVersionCode = 2
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,22 +1,21 @@
package eu.kanade.tachiyomi.extension.fr.animesama package eu.kanade.tachiyomi.extension.fr.animesama
import android.net.Uri
import eu.kanade.tachiyomi.network.GET 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.FilterList
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString import okhttp3.FormBody
import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
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
class AnimeSama : ParsedHttpSource() { class AnimeSama : ParsedHttpSource() {
@ -24,80 +23,68 @@ class AnimeSama : ParsedHttpSource() {
override val baseUrl = "https://anime-sama.fr" override val baseUrl = "https://anime-sama.fr"
val cdn_url = "https://cdn.statically.io/gh/Anime-Sama/IMG/img/animes/animes%20icones%20carr%C3%A9/" private val cdn = "$baseUrl/s2/scans/"
override val lang = "fr" override val lang = "fr"
override val supportsLatest = true override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
override fun headersBuilder(): Headers.Builder = super.headersBuilder() override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept-Language", "fr-FR") .add("Accept-Language", "fr-FR")
// Popular // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/scan", headers) return GET("$baseUrl/catalogue/", headers)
} }
override fun popularMangaSelector() = "figure.figure" override fun popularMangaSelector() = ".cardListAnime.Scans"
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply { return SManga.create().apply {
title = element.select("figcaption").text() title = element.select("h1").text()
setUrlWithoutDomain(element.select("a").attr("href")) setUrlWithoutDomain(element.select("a").attr("href"))
thumbnail_url = element.select("a > img").attr("src") thumbnail_url = element.select("img").attr("src")
} }
} }
override fun popularMangaNextPageSelector(): String? = null override fun popularMangaNextPageSelector(): String? = null
// Latest // Latest
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
return GET(baseUrl, headers)
}
override fun latestUpdatesSelector() = "div.container-fluid:nth-child(15) > div:nth-child(1) figure" override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
return SManga.create().apply {
title = element.select("figcaption").text().replace("\\nScan\\n", "")
setUrlWithoutDomain(cdn_url + title.replace(" ", "-").trim() + "carre.jpg")
thumbnail_url = element.select("img").attr("src")
}
}
override fun latestUpdatesNextPageSelector(): String? = null override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
// Search // Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse("$baseUrl/search/search.php").buildUpon() val url = "$baseUrl/template-php/defaut/fetch.php"
.appendQueryParameter("terme", query + " [SCANS]") val formBody = FormBody.Builder()
.appendQueryParameter("s", "Search") .add("query", query)
return GET(uri.toString(), headers) .build()
return POST(url, headers, formBody)
} }
override fun searchMangaSelector() = "div.media-body" override fun searchMangaSelector() = "a[href*='catalogue']"
override fun searchMangaNextPageSelector(): String? = null override fun searchMangaNextPageSelector(): String? = null
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return SManga.create().apply { return SManga.create().apply {
title = element.select("h5").text() title = element.select("h3").text()
setUrlWithoutDomain(element.select("a").attr("href")) setUrlWithoutDomain(element.attr("href"))
thumbnail_url = thumbnail_url = element.select("img").attr("src")
cdn_url + title.replace(
" [SCANS]",
"",
).replace(" ", "-").trim() + "carre.jpg"
} }
} }
// Details // Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select("div.carousel-item:nth-child(1) > div:nth-child(2) > h5:nth-child(1)").text() description = document.select("#sousBlocMilieu > div h2:contains(Synopsis)+p").text()
description = document.select("div.carousel-item:nth-child(2) > div:nth-child(2) > p:nth-child(1)").text() genre = document.select("#sousBlocMilieu > div h2:contains(Genres)+a").text()
thumbnail_url = document.select("div.carousel-item:nth-child(1) > img:nth-child(1)").attr("src") title = document.select("#titreOeuvre").text()
genre = document.select("div.carousel-item:nth-child(2) > div:nth-child(2) > p:nth-child(2)").text().replace("Genres : ", "") thumbnail_url = document.select("#coverOeuvre").attr("src")
} }
// Chapters // Chapters
@ -105,58 +92,109 @@ class AnimeSama : ParsedHttpSource() {
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException() override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
fun String.containsMultipleTimes(search: String): Boolean {
val regex = Regex(search)
val matches = regex.findAll(this)
val count = matches.count()
return count > 1
}
override fun chapterListRequest(manga: SManga): Request {
val url = "$baseUrl${manga.url}/scan/vf"
return GET(url, headers)
}
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() var document = response.asJsoup()
val javascriptUrl = document.select("body > script:nth-child(3)").attr("abs:src")
val newHeaders = headersBuilder() val baseChapterUrl = "episodes.js?title=" + document.select("#titreOeuvre").text()
.add("Accept-Version", "v1")
.build()
val request = GET(javascriptUrl, newHeaders) val requestToFetchChapters = GET("${document.baseUri()}/$baseChapterUrl", headers)
val responsejs = client.newCall(request).execute() val javascriptFile = client.newCall(requestToFetchChapters).execute()
val jsonDataString = responsejs.body.string() var javascriptFileContent = javascriptFile.body.string()
return jsonDataString val parsedJavascriptFileToJson = javascriptFileContent
.split(" ", ",") .split(" ", ",")
.filter { it.contains("eps") && !it.contains("drive.google.com") } .filter { it.contains("eps") && !it.contains("drive.google.com") }
.mapNotNull { it.replace("=", "").replace("eps", "").toIntOrNull() } .mapNotNull { it.replace("=", "").replace("eps", "").toIntOrNull() }
.sorted() .sorted()
.map { chapter -> .map { chapter ->
SChapter.create().apply { }.asReversed()
name = "Chapitre $chapter" var parsedChapterList: MutableList<SChapter> = ArrayList()
setUrlWithoutDomain(javascriptUrl + "/$chapter") var chapterDelay = 0
var scriptContent = document.select("script:containsData(resetListe\\(\\))").toString()
if (scriptContent.containsMultipleTimes("resetListe()")) {
var scriptCommandList = document.html().split(";")
var createListRegex = Regex("""creerListe\((\d+,\s*\d+)\)""")
var specialRegex = Regex("""newSP\((\d+(\.\d+)?)\)""")
scriptCommandList.forEach { command ->
when {
createListRegex.find(command) != null -> {
var data = createListRegex.find(command)!!.groupValues[1].split(",")
var start = data[0].replace(" ", "").toInt()
var end = data[1].replace(" ", "").toInt()
for (i in start..end) {
parsedChapterList.add(
SChapter.create().apply {
name = "Chapitre " + i
setUrlWithoutDomain(document.baseUri() + "/" + baseChapterUrl + "&id=${parsedChapterList.size + 1}")
},
)
}
}
specialRegex.find(command) != null -> {
var title = specialRegex.find(command)!!.groupValues[1]
parsedChapterList.add(
SChapter.create().apply {
name = "Chapitre " + title
setUrlWithoutDomain(document.baseUri() + "/" + baseChapterUrl + "&id=${parsedChapterList.size + 1}")
},
)
chapterDelay++
}
/* The site contains an as-yet unused command called "newSPE", and as I have no concrete examples of its use, I haven't implemented it yet. */
} }
} }
}
for (index in parsedChapterList.size..parsedJavascriptFileToJson.size - 1) {
parsedChapterList.add(
SChapter.create().apply {
name = "Chapitre " + (parsedChapterList.size + 1 - chapterDelay)
setUrlWithoutDomain(document.baseUri() + "/" + baseChapterUrl + "&id=${parsedChapterList.size + 1}")
},
)
}
return parsedChapterList.asReversed()
} }
// Pages // Pages
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val url = document.baseUri().split("/") val url = document.baseUri().toHttpUrl()
val javascriptUrlFinal = url.subList(0, url.size - 1).joinToString("/")
val javascriptResponse = checkJavascript(javascriptUrlFinal)
val jsonDataString = javascriptResponse.body.string()
val episode = url[url.size - 1] val title = url.queryParameter("title")
val allEpisodes = jsonDataString.split("var") val chapter = url.queryParameter("id")
val theEpisode = allEpisodes.firstOrNull { it.contains("eps$episode") } val allChapters = document.body().toString().split("var")
val chapterImageListString = allChapters.firstOrNull { it.contains("eps$chapter=") }
?: return emptyList() ?: return emptyList()
val final_list = theEpisode val chapterImageListParsed = chapterImageListString
.substringAfter("[") .substringAfter("[")
.substringBefore("]") .substringBefore("]")
.split(",")
return final_list val image_list = mutableListOf<Page>()
.substring(0, final_list.lastIndexOf(",")).replace("""'""".toRegex(), "\"")
.let { json.decodeFromString<List<String>>("[$it]") }
.mapIndexed { i, imageUrl -> Page(i, "", imageUrl) }
}
private fun checkJavascript(url: String): Response { for (index in 1 until chapterImageListParsed.size) {
val request = GET(url, headers) image_list.add(
Page(index, imageUrl = "$cdn$title/$chapter/$index.jpg"),
)
}
return client.newCall(request).execute() return image_list
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()