AnimeSama: Switch to New Endpoint and Direct AnimeSama Link Integration (#10802)
* Open URL in apps + new way to get all chapters * AnimeSama: Update extVersionCode * AnimeSama: Changes requested by vetleledaal * AnimeSama: Fix special chapters that could not be opened
This commit is contained in:
parent
3b0eb9a789
commit
0c8a27b820
23
src/fr/animesama/AndroidManifest.xml
Normal file
23
src/fr/animesama/AndroidManifest.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".fr.animesama.AnimeSamaUrlActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="anime-sama.fr"
|
||||||
|
android:pathPattern="/catalogue/..*"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'AnimeSama'
|
extName = 'AnimeSama'
|
||||||
extClass = '.AnimeSama'
|
extClass = '.AnimeSama'
|
||||||
extVersionCode = 7
|
extVersionCode = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|||||||
@ -2,9 +2,11 @@ package eu.kanade.tachiyomi.extension.fr.animesama
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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.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
|
||||||
@ -12,6 +14,8 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
|||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
@ -20,6 +24,7 @@ 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 rx.Observable
|
||||||
|
|
||||||
class AnimeSama : ParsedHttpSource() {
|
class AnimeSama : ParsedHttpSource() {
|
||||||
|
|
||||||
@ -96,7 +101,7 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
title = element.select("h1").text()
|
title = element.select("h1").text()
|
||||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||||
thumbnail_url = element.select("img").attr("src")
|
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +118,7 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
title = element.select("h1").text()
|
title = element.select("h1").text()
|
||||||
setUrlWithoutDomain(element.select("a").attr("href").removeSuffix("scan/vf/"))
|
setUrlWithoutDomain(element.select("a").attr("href").removeSuffix("scan/vf/"))
|
||||||
thumbnail_url = element.select("img").attr("src")
|
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +140,51 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
return GET(url, headers)
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun detailsParse(response: Response): SManga? {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val scriptContent = document.select("script:containsData(panneauScan(\"nom\", \"url\"))").toString()
|
||||||
|
val splitedContent = scriptContent.split(";").toMutableList()
|
||||||
|
// Remove exemple
|
||||||
|
splitedContent.removeAt(0)
|
||||||
|
|
||||||
|
val pattern = """panneauScan\("(.+?)", "(.+?)"\)""".toRegex()
|
||||||
|
val numberOfScans = splitedContent.count { line ->
|
||||||
|
val matchResult = pattern.find(line)
|
||||||
|
matchResult != null && !matchResult.groupValues[2].contains("va")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numberOfScans == 0) return null
|
||||||
|
|
||||||
|
val manga: SManga = SManga.create()
|
||||||
|
manga.description = document.select("#sousBlocMiddle > div h2:contains(Synopsis)+p").text()
|
||||||
|
manga.genre = document.select("#sousBlocMiddle > div h2:contains(Genres)+a").text()
|
||||||
|
manga.title = document.select("#titreOeuvre").text()
|
||||||
|
manga.thumbnail_url = document.selectFirst("#coverOeuvre")?.absUrl("src")
|
||||||
|
manga.setUrlWithoutDomain(document.baseUri())
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
return if (query.startsWith("ID:")) {
|
||||||
|
val id = query.substringAfterLast("ID:")
|
||||||
|
|
||||||
|
val mangaUrl = "$baseUrl/catalogue".toHttpUrl()
|
||||||
|
.newBuilder()
|
||||||
|
.addPathSegment(id)
|
||||||
|
.build()
|
||||||
|
val requestToCheckManga = GET(mangaUrl, headers)
|
||||||
|
client.newCall(requestToCheckManga).asObservableSuccess().map {
|
||||||
|
MangasPage(
|
||||||
|
listOfNotNull(detailsParse(it)),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.fetchSearchManga(page, query, filters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
|
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
|
||||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
@ -144,7 +194,7 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
description = document.select("#sousBlocMiddle > div h2:contains(Synopsis)+p").text()
|
description = document.select("#sousBlocMiddle > div h2:contains(Synopsis)+p").text()
|
||||||
genre = document.select("#sousBlocMiddle > div h2:contains(Genres)+a").text()
|
genre = document.select("#sousBlocMiddle > div h2:contains(Genres)+a").text()
|
||||||
title = document.select("#titreOeuvre").text()
|
title = document.select("#titreOeuvre").text()
|
||||||
thumbnail_url = document.select("#coverOeuvre").attr("src")
|
thumbnail_url = document.selectFirst("#coverOeuvre")?.absUrl("src")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
// Chapters
|
||||||
@ -162,22 +212,16 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
private fun parseChapterFromResponse(response: Response, translationName: String): List<SChapter> {
|
private fun parseChapterFromResponse(response: Response, translationName: String): List<SChapter> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
|
|
||||||
val chapterUrl = document.baseUri().toHttpUrl()
|
val title = document.select("#titreOeuvre").text()
|
||||||
|
val chapterUrl = "$baseUrl/s2/scans/get_nb_chap_et_img.php".toHttpUrl()
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.query(null)
|
.addQueryParameter("oeuvre", title)
|
||||||
.addPathSegment("episodes.js")
|
|
||||||
.addQueryParameter("title", document.select("#titreOeuvre").text())
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val requestToFetchChapters = GET(chapterUrl, headers)
|
val requestToFetchChapters = GET(chapterUrl, headers)
|
||||||
val javascriptFile = client.newCall(requestToFetchChapters).execute()
|
|
||||||
val javascriptFileContent = javascriptFile.body.string()
|
|
||||||
|
|
||||||
val parsedJavascriptFileToJson = javascriptFileContent
|
val apiNbChapImgResponse = client.newCall(requestToFetchChapters).execute()
|
||||||
.let { Regex("""eps(\d+)""").findAll(it) }
|
val apiNbChapImgJson = Json.decodeFromString<Map<String, Int>>(apiNbChapImgResponse.body.string())
|
||||||
.map { it.groupValues[1].toInt() }
|
|
||||||
.distinct() // Remove duplicate episodes
|
|
||||||
.sortedDescending().toList()
|
|
||||||
val parsedChapterList: MutableList<SChapter> = ArrayList()
|
val parsedChapterList: MutableList<SChapter> = ArrayList()
|
||||||
var chapterDelay = 0
|
var chapterDelay = 0
|
||||||
|
|
||||||
@ -197,18 +241,18 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
parsedChapterList.add(
|
parsedChapterList.add(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapitre $i"
|
name = "Chapitre $i"
|
||||||
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
|
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).addQueryParameter("title", title).build().toString())
|
||||||
scanlator = translationName
|
scanlator = translationName
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
specialRegex.find(command) != null -> {
|
specialRegex.find(command) != null -> {
|
||||||
val title = specialRegex.find(command)!!.groupValues[1]
|
val chapterTitle = specialRegex.find(command)!!.groupValues[1]
|
||||||
parsedChapterList.add(
|
parsedChapterList.add(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapitre $title"
|
name = "Chapitre $chapterTitle"
|
||||||
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
|
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).addQueryParameter("title", title).build().toString())
|
||||||
scanlator = translationName
|
scanlator = translationName
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -218,11 +262,11 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (index in parsedChapterList.size until parsedJavascriptFileToJson.size) {
|
(parsedChapterList.size until apiNbChapImgJson.size).forEach { index ->
|
||||||
parsedChapterList.add(
|
parsedChapterList.add(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapitre " + (parsedChapterList.size + 1 - chapterDelay)
|
name = "Chapitre " + (parsedChapterList.size + 1 - chapterDelay)
|
||||||
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
|
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).addQueryParameter("title", title).build().toString())
|
||||||
scanlator = translationName
|
scanlator = translationName
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -239,14 +283,14 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
splitedContent.removeAt(0)
|
splitedContent.removeAt(0)
|
||||||
|
|
||||||
val parsedChapterList: MutableList<SChapter> = mutableListOf()
|
val parsedChapterList: MutableList<SChapter> = mutableListOf()
|
||||||
|
val pattern = """panneauScan\("(.+?)", "(.+?)"\)""".toRegex()
|
||||||
|
val scanPattern = Regex("""(Scans|\(|\))""")
|
||||||
splitedContent.forEach { line ->
|
splitedContent.forEach { line ->
|
||||||
val pattern = """panneauScan\("(.+?)", "(.+?)"\)""".toRegex()
|
|
||||||
val matchResult = pattern.find(line)
|
val matchResult = pattern.find(line)
|
||||||
if (matchResult != null) {
|
if (matchResult != null) {
|
||||||
val (scanTitle, scanUrl) = matchResult.destructured
|
val (scanTitle, scanUrl) = matchResult.destructured
|
||||||
if (!scanUrl.contains("va")) {
|
if (!scanUrl.contains("va")) {
|
||||||
val scanlatorGroup = scanTitle.replace(Regex("""(Scans|\(|\))"""), "").trim()
|
val scanlatorGroup = scanTitle.replace(scanPattern, "").trim()
|
||||||
val fetchExistentSubMangas = GET(
|
val fetchExistentSubMangas = GET(
|
||||||
url.newBuilder()
|
url.newBuilder()
|
||||||
.addPathSegments(
|
.addPathSegments(
|
||||||
@ -260,7 +304,7 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedChapterList.sortBy { chapter -> ("$baseUrl${chapter.url}").toHttpUrl().queryParameter("id")?.toIntOrNull() }
|
parsedChapterList.sortBy { chapter -> "$baseUrl${chapter.url}".toHttpUrl().queryParameter("id")?.toIntOrNull() }
|
||||||
|
|
||||||
return parsedChapterList.asReversed()
|
return parsedChapterList.asReversed()
|
||||||
}
|
}
|
||||||
@ -272,32 +316,19 @@ class AnimeSama : ParsedHttpSource() {
|
|||||||
val title = url.queryParameter("title")
|
val title = url.queryParameter("title")
|
||||||
val chapter = url.queryParameter("id")
|
val chapter = url.queryParameter("id")
|
||||||
|
|
||||||
val documentString = document.body().toString()
|
val chapterUrl = "$baseUrl/s2/scans/get_nb_chap_et_img.php".toHttpUrl()
|
||||||
|
.newBuilder()
|
||||||
|
.addQueryParameter("oeuvre", title)
|
||||||
|
.build()
|
||||||
|
val requestToFetchChapters = GET(chapterUrl, headers)
|
||||||
|
|
||||||
val allChapters: Map<Int, Int> = Regex("""eps(\d+)\s*(?:=\s*\[(.*?)]|\.length\s*=\s*(\d+))""")
|
val apiNbChapImgResponse = client.newCall(requestToFetchChapters).execute()
|
||||||
.findAll(documentString)
|
val apiNbChapImgJson = Json.decodeFromString<Map<String, Int>>(apiNbChapImgResponse.body.string())
|
||||||
.associate { match ->
|
val imageCount = apiNbChapImgJson[chapter] ?: 0
|
||||||
val episode = match.groupValues[1].toInt()
|
|
||||||
val arrayContent = match.groupValues[2]
|
|
||||||
val explicitLength = match.groupValues[3]
|
|
||||||
|
|
||||||
val length = when {
|
val imageList = (1..imageCount).map { index ->
|
||||||
explicitLength.isNotEmpty() -> explicitLength.toInt()
|
Page(index, imageUrl = "$cdn$title/$chapter/$index.jpg")
|
||||||
arrayContent.isNotEmpty() -> arrayContent.split(Regex(",\\s*")).count { it.isNotBlank() }
|
}.toMutableList()
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
episode to length
|
|
||||||
}
|
|
||||||
|
|
||||||
val chapterSize = allChapters[chapter?.toInt()] ?: 1
|
|
||||||
|
|
||||||
val imageList = mutableListOf<Page>()
|
|
||||||
for (index in 1 until chapterSize + 1) {
|
|
||||||
imageList.add(
|
|
||||||
Page(index, imageUrl = "$cdn$title/$chapter/$index.jpg"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageList
|
return imageList
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.fr.animesama
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Springboard that accepts https://anime-sama.fr/catalogue/xxxxxx/ intents and redirects them to
|
||||||
|
* the main Tachiyomi process.
|
||||||
|
*/
|
||||||
|
class AnimeSamaUrlActivity : Activity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val pathSegments = intent?.data?.pathSegments
|
||||||
|
if (pathSegments != null && pathSegments.size > 1) {
|
||||||
|
val id = pathSegments[1]
|
||||||
|
val mainIntent = Intent().apply {
|
||||||
|
action = "eu.kanade.tachiyomi.SEARCH"
|
||||||
|
putExtra("query", "ID:$id")
|
||||||
|
putExtra("filter", packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(mainIntent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Log.e("AnimeSamaUrlActivity", e.toString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e("AnimeSamaUrlActivity", "could not parse uri from intent $intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
finish()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user